<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko">
	<id>https://clbiwiki.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Nxdsxn</id>
	<title>COASTLINE: BLACK ICE - 사용자 기여 [ko]</title>
	<link rel="self" type="application/atom+xml" href="https://clbiwiki.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Nxdsxn"/>
	<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php/%ED%8A%B9%EC%88%98:%EA%B8%B0%EC%97%AC/Nxdsxn"/>
	<updated>2026-07-18T13:15:55Z</updated>
	<subtitle>사용자 기여</subtitle>
	<generator>MediaWiki 1.41.5</generator>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:PortalSectionNav.js&amp;diff=4694</id>
		<title>미디어위키:PortalSectionNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:PortalSectionNav.js&amp;diff=4694"/>
		<updated>2026-07-17T09:49:33Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-fixed / js/PortalSectionNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
PortalSectionNav&lt;br /&gt;
대문 상단 카테고리 하단 부착형 canvas 픽셀 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var COMPONENT = &#039;portal-section-nav&#039;;&lt;br /&gt;
    var VERSION = &#039;20260717-canvas-pixel-001&#039;;&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
    var ITEMS = [&lt;br /&gt;
        { key:&#039;black-ice&#039;, label:&#039;블랙 아이스&#039;, title:&#039;대문&#039;, slant:&#039;left&#039; },&lt;br /&gt;
        { key:&#039;anecdote&#039;, label:&#039;에넥도트&#039;, href:&#039;/index.php/특수:모든문서?namespace=3000&#039;, slant:&#039;right&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function api() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildWikiPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) return window.buildWikiPath(title);&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal, topMount) {&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
        }&lt;br /&gt;
        if (mount.parentNode !== topMount.parentNode || topMount.nextSibling !== mount) {&lt;br /&gt;
            topMount.parentNode.insertBefore(mount, topMount.nextSibling);&lt;br /&gt;
        }&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sideAt(geometry, y, side) {&lt;br /&gt;
        var step = Math.floor((y + geometry.phase) / api().FRAME.SLANT_RISE);&lt;br /&gt;
        return side === &#039;left&#039;&lt;br /&gt;
            ? geometry.leftSeamX + step&lt;br /&gt;
            : geometry.rightSeamX - step;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function outerGeometry(seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var phase = seams.bottomStartAbsY % frame.FRAME.SLANT_RISE;&lt;br /&gt;
        return {&lt;br /&gt;
            width:seams.width,&lt;br /&gt;
            height:frame.DIMENSIONS.ROW_HEIGHT,&lt;br /&gt;
            phase:phase,&lt;br /&gt;
            leftSeamX:Math.max(0, Math.round(seams.settingsLeftSeamX)),&lt;br /&gt;
            rightSeamX:Math.min(seams.width - 1, Math.round(seams.corpRightSeamX))&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function paintOuterFrame(image, geometry) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var width = geometry.width;&lt;br /&gt;
        var height = geometry.height;&lt;br /&gt;
        var body = [];&lt;br /&gt;
        var src;&lt;br /&gt;
        var y;&lt;br /&gt;
        var x;&lt;br /&gt;
&lt;br /&gt;
        function inside(px, py) {&lt;br /&gt;
            if (py &amp;lt; 0 || py &amp;gt;= height) return false;&lt;br /&gt;
            return px &amp;gt;= sideAt(geometry, py, &#039;left&#039;) &amp;amp;&amp;amp; px &amp;lt;= sideAt(geometry, py, &#039;right&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        for (y = 0; y &amp;lt; height; y++) {&lt;br /&gt;
            body[y] = [];&lt;br /&gt;
            for (x = 0; x &amp;lt; width; x++) {&lt;br /&gt;
                if (!inside(x, y)) {&lt;br /&gt;
                    body[y][x] = &#039;BG&#039;;&lt;br /&gt;
                    continue;&lt;br /&gt;
                }&lt;br /&gt;
                body[y][x] = !(inside(x, y - 1) &amp;amp;&amp;amp; inside(x, y + 1) &amp;amp;&amp;amp; inside(x - 1, y) &amp;amp;&amp;amp; inside(x + 1, y))&lt;br /&gt;
                    ? &#039;B&#039;&lt;br /&gt;
                    : &#039;S&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        src = body.map(function (row) { return row.slice(); });&lt;br /&gt;
        for (y = 0; y &amp;lt; height; y++) {&lt;br /&gt;
            for (x = 0; x &amp;lt; width; x++) {&lt;br /&gt;
                if (src[y][x] !== &#039;S&#039;) continue;&lt;br /&gt;
                var tb = y &amp;gt; 0 &amp;amp;&amp;amp; src[y - 1][x] === &#039;B&#039;;&lt;br /&gt;
                var rb = x &amp;lt; width - 1 &amp;amp;&amp;amp; src[y][x + 1] === &#039;B&#039;;&lt;br /&gt;
                var bb = y &amp;lt; height - 1 &amp;amp;&amp;amp; src[y + 1][x] === &#039;B&#039;;&lt;br /&gt;
                var lb = x &amp;gt; 0 &amp;amp;&amp;amp; src[y][x - 1] === &#039;B&#039;;&lt;br /&gt;
                if (tb || rb) body[y][x] = &#039;H&#039;;&lt;br /&gt;
                else if (bb || lb) body[y][x] = &#039;D&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        frame.paintGrid(image, width, height, body, 0, 0, true);&lt;br /&gt;
&lt;br /&gt;
        /* 상단 9px은 상단층과 같은 #1d1d1d 접합면으로 연다. */&lt;br /&gt;
        for (y = 0; y &amp;lt; frame.DIMENSIONS.OVERLAP; y++) {&lt;br /&gt;
            var left = sideAt(geometry, y, &#039;left&#039;);&lt;br /&gt;
            var right = sideAt(geometry, y, &#039;right&#039;);&lt;br /&gt;
            for (x = left + 1; x &amp;lt; right; x++) frame.setPixel(image, width, height, x, y, &#039;S&#039;);&lt;br /&gt;
            frame.setPixel(image, width, height, left, y, &#039;D&#039;);&lt;br /&gt;
            frame.setPixel(image, width, height, right, y, &#039;H&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* 프레임 안쪽 우물. 위·아래 2px 간격은 순수 #080808로 남긴다. */&lt;br /&gt;
        for (y = frame.DIMENSIONS.WELL_TOP; y &amp;lt; frame.DIMENSIONS.WELL2_BOTTOM; y++) {&lt;br /&gt;
            var wellLeft = sideAt(geometry, y, &#039;left&#039;) + frame.DIMENSIONS.WELL_TOP;&lt;br /&gt;
            var wellRight = sideAt(geometry, y, &#039;right&#039;) - frame.DIMENSIONS.WELL_TOP;&lt;br /&gt;
            for (x = wellLeft; x &amp;lt;= wellRight; x++) frame.setPixel(image, width, height, x, y, &#039;BG&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function paintProjectPillar(image, geometry, seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var absoluteStep = Math.floor(&lt;br /&gt;
            seams.bottomStartAbsY / frame.FRAME.SLANT_RISE&lt;br /&gt;
        );&lt;br /&gt;
        var leftSeam = seams.projectLeftOriginX + absoluteStep;&lt;br /&gt;
        var rightSeam = seams.projectRightOriginX - absoluteStep;&lt;br /&gt;
        var y;&lt;br /&gt;
        var x;&lt;br /&gt;
&lt;br /&gt;
        for (y = 0; y &amp;lt; geometry.height; y++) {&lt;br /&gt;
            var step = Math.floor((y + geometry.phase) / frame.FRAME.SLANT_RISE);&lt;br /&gt;
            var left = leftSeam + step;&lt;br /&gt;
            var right = rightSeam - step;&lt;br /&gt;
            for (x = left; x &amp;lt;= right; x++) {&lt;br /&gt;
                frame.setPixel(image, geometry.width, geometry.height, x, y, &#039;S&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            frame.setPixel(image, geometry.width, geometry.height, left, y, &#039;D&#039;);&lt;br /&gt;
            frame.setPixel(image, geometry.width, geometry.height, right, y, &#039;H&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function splitButtons(geometry) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var left = sideAt(geometry, d.BUTTON_EDGE_TOP, &#039;left&#039;) + d.WELL_TOP;&lt;br /&gt;
        var right = sideAt(geometry, d.BUTTON_EDGE_TOP, &#039;right&#039;) - d.WELL_TOP;&lt;br /&gt;
        var available = Math.max(12, right - left + 1);&lt;br /&gt;
        var btnW = Math.max(4, Math.floor((available - frame.FRAME.GAP) / 2));&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                x:left,&lt;br /&gt;
                y:d.BUTTON_EDGE_TOP,&lt;br /&gt;
                btnW:btnW,&lt;br /&gt;
                pixelWidth:btnW + 2,&lt;br /&gt;
                pixelHeight:d.BUTTON_TOTAL,&lt;br /&gt;
                slant:&#039;left&#039;,&lt;br /&gt;
                phase:geometry.phase&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                x:left + btnW + frame.FRAME.GAP,&lt;br /&gt;
                y:d.BUTTON_EDGE_TOP,&lt;br /&gt;
                btnW:btnW,&lt;br /&gt;
                pixelWidth:btnW + 2,&lt;br /&gt;
                pixelHeight:d.BUTTON_TOTAL,&lt;br /&gt;
                slant:&#039;right&#039;,&lt;br /&gt;
                phase:geometry.phase&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function labelCenter(button) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var mask = frame.buttonMask(&lt;br /&gt;
            button.btnW,&lt;br /&gt;
            Math.floor(frame.FRAME.BUTTON_FACE / 2),&lt;br /&gt;
            button.slant,&lt;br /&gt;
            button.phase&lt;br /&gt;
        );&lt;br /&gt;
        return button.x + Math.round((mask.left + mask.right) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function draw(canvas, geometry, buttons, seams, hoverIndex) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        var image = frame.createImage(ctx, geometry.width, geometry.height, &#039;BG&#039;);&lt;br /&gt;
&lt;br /&gt;
        paintOuterFrame(image, geometry);&lt;br /&gt;
        buttons.forEach(function (button) {&lt;br /&gt;
            var grid = frame.buildButtonPixels(&lt;br /&gt;
                button.btnW,&lt;br /&gt;
                frame.FRAME.BUTTON_FACE,&lt;br /&gt;
                button.slant,&lt;br /&gt;
                button.phase&lt;br /&gt;
            );&lt;br /&gt;
            frame.paintGrid(&lt;br /&gt;
                image,&lt;br /&gt;
                geometry.width,&lt;br /&gt;
                geometry.height,&lt;br /&gt;
                grid,&lt;br /&gt;
                button.x,&lt;br /&gt;
                button.y,&lt;br /&gt;
                true&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
        paintProjectPillar(image, geometry, seams);&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
&lt;br /&gt;
        buttons.forEach(function (button, index) {&lt;br /&gt;
            frame.drawLabel(&lt;br /&gt;
                ctx,&lt;br /&gt;
                ITEMS[index].label,&lt;br /&gt;
                labelCenter(button),&lt;br /&gt;
                Math.round((frame.DIMENSIONS.BUTTON_SURFACE_TOP + frame.DIMENSIONS.BUTTON_SURFACE_BOTTOM) / 2) + 1,&lt;br /&gt;
                hoverIndex === index,&lt;br /&gt;
                geometry.width &amp;lt;= 820 ? 11 : 12&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createHit(item, index, button, nav, redraw) {&lt;br /&gt;
        var anchor = document.createElement(&#039;a&#039;);&lt;br /&gt;
        var label = document.createElement(&#039;span&#039;);&lt;br /&gt;
        anchor.className = &#039;portal-frame-hit portal-section-anchor&#039;;&lt;br /&gt;
        anchor.href = item.href || buildWikiPath(item.title);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, item.key);&lt;br /&gt;
        anchor.style.left = button.x + &#039;px&#039;;&lt;br /&gt;
        anchor.style.top = button.y + &#039;px&#039;;&lt;br /&gt;
        anchor.style.width = button.pixelWidth + &#039;px&#039;;&lt;br /&gt;
        anchor.style.height = button.pixelHeight + &#039;px&#039;;&lt;br /&gt;
        label.className = &#039;portal-frame-sr-label&#039;;&lt;br /&gt;
        label.textContent = item.label;&lt;br /&gt;
        anchor.appendChild(label);&lt;br /&gt;
        anchor.addEventListener(&#039;mouseenter&#039;, function () {&lt;br /&gt;
            nav.__frameHoverIndex = index;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;mouseleave&#039;, function () {&lt;br /&gt;
            if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;focus&#039;, function () {&lt;br /&gt;
            nav.__frameHoverIndex = index;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
            if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        return anchor;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPortal(portal, seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var current = seams || (frame &amp;amp;&amp;amp; frame.seams);&lt;br /&gt;
        var topMount;&lt;br /&gt;
        var mount;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var inner;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var hitLayer;&lt;br /&gt;
        var geometry;&lt;br /&gt;
        var buttons;&lt;br /&gt;
        var redraw;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !portal || !current || !current.width) return false;&lt;br /&gt;
        topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        if (!topMount) return false;&lt;br /&gt;
        mount = ensureMount(portal, topMount);&lt;br /&gt;
        geometry = outerGeometry(current);&lt;br /&gt;
        buttons = splitButtons(geometry);&lt;br /&gt;
        if (&lt;br /&gt;
            mount.getAttribute(&#039;data-portal-section-width&#039;) === String(current.width) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-portal-section-phase&#039;) === String(geometry.phase) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-portal-section-scale&#039;) === String(frame.getShellScale()) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.querySelector(&#039;.portal-section-canvas&#039;)&lt;br /&gt;
        ) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        nav.className = &#039;portal-section-nav portal-frame-canvas-nav&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, &#039;작품 영역&#039;);&lt;br /&gt;
        nav.__frameHoverIndex = -1;&lt;br /&gt;
&lt;br /&gt;
        inner = document.createElement(&#039;div&#039;);&lt;br /&gt;
        inner.className = &#039;portal-frame-counter-inner portal-section-counter-inner&#039;;&lt;br /&gt;
        inner.style.width = geometry.width + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        canvas = frame.createCanvas(&#039;portal-frame-canvas portal-section-canvas&#039;, geometry.width, geometry.height);&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-width&#039;, String(geometry.width));&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-height&#039;, String(geometry.height));&lt;br /&gt;
&lt;br /&gt;
        hitLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        hitLayer.className = &#039;portal-frame-hit-layer&#039;;&lt;br /&gt;
        hitLayer.style.width = geometry.width + &#039;px&#039;;&lt;br /&gt;
        hitLayer.style.height = geometry.height + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        redraw = function () { draw(canvas, geometry, buttons, current, nav.__frameHoverIndex); };&lt;br /&gt;
        ITEMS.forEach(function (item, index) {&lt;br /&gt;
            hitLayer.appendChild(createHit(item, index, buttons[index], nav, redraw));&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        inner.appendChild(canvas);&lt;br /&gt;
        inner.appendChild(hitLayer);&lt;br /&gt;
        nav.appendChild(inner);&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        frame.applyCounterScale(&lt;br /&gt;
            [mount, nav],&lt;br /&gt;
            inner,&lt;br /&gt;
            geometry.height,&lt;br /&gt;
            frame.DIMENSIONS.OVERLAP&lt;br /&gt;
        );&lt;br /&gt;
        redraw();&lt;br /&gt;
&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-rendered&#039;, &#039;canvas&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-width&#039;, String(current.width));&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-phase&#039;, String(geometry.phase));&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-scale&#039;, String(frame.getShellScale()));&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectPortals(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var portals = [];&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)) portals.push(scope);&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;.main-portal&#039;), function (portal) {&lt;br /&gt;
            if (portals.indexOf(portal) === -1) portals.push(portal);&lt;br /&gt;
        });&lt;br /&gt;
        return portals;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root, seams) {&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
        collectPortals(root).forEach(function (portal) {&lt;br /&gt;
            if (renderPortal(portal, seams)) rendered += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, seams) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document, seams);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (api()) {&lt;br /&gt;
        api().onSeams(function (seams) { schedule(document, seams); });&lt;br /&gt;
        api().onScale(function () { schedule(document); });&lt;br /&gt;
    }&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            schedule(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.PortalSectionNav = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        render:renderPortal,&lt;br /&gt;
        renderAll:renderAll&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:FrameGeometry.js&amp;diff=4693</id>
		<title>미디어위키:FrameGeometry.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:FrameGeometry.js&amp;diff=4693"/>
		<updated>2026-07-17T09:49:33Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-fixed / js/FrameGeometry.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
FrameGeometry&lt;br /&gt;
대문 프레임 공통 canvas 픽셀 렌더러&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (w, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var FRAME = {&lt;br /&gt;
        BORDER:1,&lt;br /&gt;
        BEVEL:1,&lt;br /&gt;
        SLANT_RISE:2,&lt;br /&gt;
        SLANT_RUN:1,&lt;br /&gt;
        BUTTON_FACE:20,&lt;br /&gt;
        FRAME_FACE:7,&lt;br /&gt;
        GAP:2,&lt;br /&gt;
        COLORS:{&lt;br /&gt;
            border:&#039;#050505&#039;,&lt;br /&gt;
            surface:&#039;#1d1d1d&#039;,&lt;br /&gt;
            well:&#039;#080808&#039;,&lt;br /&gt;
            highlight:&#039;#555555&#039;,&lt;br /&gt;
            shadow:&#039;#101010&#039;,&lt;br /&gt;
            label:&#039;#e2e2e2&#039;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    var DIMENSIONS = {&lt;br /&gt;
        FRAME_SURFACE_TOP:1,&lt;br /&gt;
        FRAME_SURFACE_BOTTOM:8,&lt;br /&gt;
        WELL_TOP:8,&lt;br /&gt;
        WELL_BOTTOM:10,&lt;br /&gt;
        BUTTON_EDGE_TOP:10,&lt;br /&gt;
        BUTTON_SURFACE_TOP:11,&lt;br /&gt;
        BUTTON_SURFACE_BOTTOM:31,&lt;br /&gt;
        BUTTON_EDGE_BOTTOM:32,&lt;br /&gt;
        WELL2_TOP:32,&lt;br /&gt;
        WELL2_BOTTOM:34,&lt;br /&gt;
        FRAME2_SURFACE_TOP:34,&lt;br /&gt;
        FRAME2_SURFACE_BOTTOM:41,&lt;br /&gt;
        ROW_HEIGHT:42,&lt;br /&gt;
        OVERLAP:9,&lt;br /&gt;
        BUTTON_TOTAL:22,&lt;br /&gt;
        BOTTOM_START_ABS_Y:33&lt;br /&gt;
    };&lt;br /&gt;
    var COL = {&lt;br /&gt;
        B:[5,5,5],&lt;br /&gt;
        S:[29,29,29],&lt;br /&gt;
        H:[85,85,85],&lt;br /&gt;
        D:[16,16,16],&lt;br /&gt;
        BG:[8,8,8],&lt;br /&gt;
        T:[226,226,226],&lt;br /&gt;
        TW:[255,255,255]&lt;br /&gt;
    };&lt;br /&gt;
    var seamListeners = [];&lt;br /&gt;
    var scaleListeners = [];&lt;br /&gt;
    var shellScale = 1;&lt;br /&gt;
&lt;br /&gt;
    DIMENSIONS.TOP_FRAME = DIMENSIONS.FRAME_SURFACE_BOTTOM;&lt;br /&gt;
    DIMENSIONS.BUTTON_TOP = DIMENSIONS.BUTTON_EDGE_TOP;&lt;br /&gt;
    DIMENSIONS.BUTTON_BOTTOM = DIMENSIONS.BUTTON_EDGE_BOTTOM;&lt;br /&gt;
    DIMENSIONS.BOTTOM_FRAME = DIMENSIONS.ROW_HEIGHT - DIMENSIONS.FRAME2_SURFACE_TOP;&lt;br /&gt;
&lt;br /&gt;
    function integer(value) {&lt;br /&gt;
        return Math.round(Number(value) || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function snapWidth(raw) {&lt;br /&gt;
        return Math.max(0, Math.floor(Number(raw) || 0));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readWidth(node) {&lt;br /&gt;
        if (!node) return 0;&lt;br /&gt;
        return snapWidth(node.clientWidth || node.offsetWidth || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readCssShellScale() {&lt;br /&gt;
        var raw = &#039;&#039;;&lt;br /&gt;
        var value;&lt;br /&gt;
        try {&lt;br /&gt;
            raw = w.getComputedStyle(document.documentElement)&lt;br /&gt;
                .getPropertyValue(&#039;--clbi-shell-scale&#039;);&lt;br /&gt;
        } catch (error) {}&lt;br /&gt;
        value = parseFloat(raw);&lt;br /&gt;
        return isFinite(value) &amp;amp;&amp;amp; value &amp;gt; 0 ? value : 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getShellScale() {&lt;br /&gt;
        var current = readCssShellScale();&lt;br /&gt;
        if (Math.abs(current - shellScale) &amp;gt; 0.0001) shellScale = current;&lt;br /&gt;
        return shellScale;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setShellScale(value) {&lt;br /&gt;
        var next = Number(value);&lt;br /&gt;
        var previous = shellScale;&lt;br /&gt;
        if (!isFinite(next) || next &amp;lt;= 0) next = 1;&lt;br /&gt;
        shellScale = next;&lt;br /&gt;
        document.documentElement.style.setProperty(&lt;br /&gt;
            &#039;--portal-frame-counter-scale&#039;,&lt;br /&gt;
            String(1 / shellScale)&lt;br /&gt;
        );&lt;br /&gt;
        if (Math.abs(previous - shellScale) &amp;lt;= 0.0001) return shellScale;&lt;br /&gt;
        scaleListeners.slice().forEach(function (listener) {&lt;br /&gt;
            try { listener(shellScale, previous); } catch (error) {}&lt;br /&gt;
        });&lt;br /&gt;
        try {&lt;br /&gt;
            document.dispatchEvent(new CustomEvent(&#039;framegeometryscale&#039;, {&lt;br /&gt;
                detail:{ scale:shellScale, previous:previous }&lt;br /&gt;
            }));&lt;br /&gt;
        } catch (error) {}&lt;br /&gt;
        return shellScale;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function onScale(listener) {&lt;br /&gt;
        if (typeof listener !== &#039;function&#039;) return function () {};&lt;br /&gt;
        scaleListeners.push(listener);&lt;br /&gt;
        return function () {&lt;br /&gt;
            var index = scaleListeners.indexOf(listener);&lt;br /&gt;
            if (index !== -1) scaleListeners.splice(index, 1);&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readRenderWidth(node) {&lt;br /&gt;
        var layoutWidth = readWidth(node);&lt;br /&gt;
        if (!layoutWidth) return 0;&lt;br /&gt;
        return Math.max(1, Math.round(layoutWidth * getShellScale()));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyCounterScale(hosts, inner, logicalHeight, overlap) {&lt;br /&gt;
        var scale = getShellScale();&lt;br /&gt;
        var inverse = 1 / scale;&lt;br /&gt;
        var hostHeight = logicalHeight / scale;&lt;br /&gt;
        var hostList = Array.isArray(hosts) ? hosts : [hosts];&lt;br /&gt;
&lt;br /&gt;
        hostList.forEach(function (host) {&lt;br /&gt;
            if (!host || !host.style) return;&lt;br /&gt;
            host.classList.add(&#039;portal-frame-counter-host&#039;);&lt;br /&gt;
            host.style.setProperty(&#039;height&#039;, hostHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
            host.style.setProperty(&#039;min-height&#039;, hostHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
            host.style.setProperty(&#039;max-height&#039;, hostHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
            host.style.setProperty(&#039;flex-basis&#039;, hostHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (hosts &amp;amp;&amp;amp; !Array.isArray(hosts) &amp;amp;&amp;amp; typeof overlap === &#039;number&#039;) {&lt;br /&gt;
            hosts.style.setProperty(&#039;margin-top&#039;, (-overlap / scale) + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
        } else if (Array.isArray(hosts) &amp;amp;&amp;amp; hosts[0] &amp;amp;&amp;amp; typeof overlap === &#039;number&#039;) {&lt;br /&gt;
            hosts[0].style.setProperty(&#039;margin-top&#039;, (-overlap / scale) + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (inner &amp;amp;&amp;amp; inner.style) {&lt;br /&gt;
            inner.classList.add(&#039;portal-frame-counter-inner&#039;);&lt;br /&gt;
            inner.style.setProperty(&#039;height&#039;, logicalHeight + &#039;px&#039;);&lt;br /&gt;
            inner.style.setProperty(&#039;transform-origin&#039;, &#039;top left&#039;);&lt;br /&gt;
            inner.style.setProperty(&#039;transform&#039;, &#039;scale(&#039; + inverse + &#039;)&#039;);&lt;br /&gt;
            inner.setAttribute(&#039;data-frame-shell-scale&#039;, String(scale));&lt;br /&gt;
        }&lt;br /&gt;
        return { scale:scale, inverse:inverse, hostHeight:hostHeight };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * 검증본의 버튼 마스크. phase는 2단 접합의 홀짝 위상만 더한다.&lt;br /&gt;
     * slant: right | left | both&lt;br /&gt;
     */&lt;br /&gt;
    function buttonMask(BTN_W, ys, slant, phase) {&lt;br /&gt;
        var step = Math.floor((ys + (phase || 0)) / 2);&lt;br /&gt;
        var left;&lt;br /&gt;
        var right;&lt;br /&gt;
        if (slant === &#039;right&#039;) {&lt;br /&gt;
            left = 0;&lt;br /&gt;
            right = BTN_W - step;&lt;br /&gt;
        } else if (slant === &#039;left&#039;) {&lt;br /&gt;
            left = step;&lt;br /&gt;
            right = BTN_W;&lt;br /&gt;
        } else {&lt;br /&gt;
            left = step;&lt;br /&gt;
            right = BTN_W - step;&lt;br /&gt;
        }&lt;br /&gt;
        return { left:left, right:right };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildButtonPixels(BTN_W, SURFACE_H, slant, phase) {&lt;br /&gt;
        var H = 1 + SURFACE_H + 1;&lt;br /&gt;
        var W = BTN_W + 2;&lt;br /&gt;
        function io(x, y) {&lt;br /&gt;
            if (y &amp;lt; 0 || y &amp;gt;= H) return false;&lt;br /&gt;
            var ys = y - 1;&lt;br /&gt;
            var ysc = ys &amp;lt; 0 ? 0 : (ys &amp;gt;= SURFACE_H ? SURFACE_H - 1 : ys);&lt;br /&gt;
            var m = buttonMask(BTN_W, ysc, slant, phase || 0);&lt;br /&gt;
            return x &amp;gt;= m.left &amp;amp;&amp;amp; x &amp;lt;= m.right;&lt;br /&gt;
        }&lt;br /&gt;
        var g = [];&lt;br /&gt;
        var src;&lt;br /&gt;
        var y;&lt;br /&gt;
        var x;&lt;br /&gt;
        for (y = 0; y &amp;lt; H; y++) {&lt;br /&gt;
            g[y] = [];&lt;br /&gt;
            for (x = 0; x &amp;lt; W; x++) {&lt;br /&gt;
                if (!io(x, y)) {&lt;br /&gt;
                    g[y][x] = &#039;BG&#039;;&lt;br /&gt;
                    continue;&lt;br /&gt;
                }&lt;br /&gt;
                var edge = !(io(x, y - 1) &amp;amp;&amp;amp; io(x, y + 1) &amp;amp;&amp;amp; io(x - 1, y) &amp;amp;&amp;amp; io(x + 1, y));&lt;br /&gt;
                g[y][x] = edge ? &#039;B&#039; : &#039;S&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        src = g.map(function (row) { return row.slice(); });&lt;br /&gt;
        for (y = 0; y &amp;lt; H; y++) {&lt;br /&gt;
            for (x = 0; x &amp;lt; W; x++) {&lt;br /&gt;
                if (src[y][x] !== &#039;S&#039;) continue;&lt;br /&gt;
                var tb = y &amp;gt; 0 &amp;amp;&amp;amp; src[y - 1][x] === &#039;B&#039;;&lt;br /&gt;
                var rb = x &amp;lt; W - 1 &amp;amp;&amp;amp; src[y][x + 1] === &#039;B&#039;;&lt;br /&gt;
                var bb = y &amp;lt; H - 1 &amp;amp;&amp;amp; src[y + 1][x] === &#039;B&#039;;&lt;br /&gt;
                var lb = x &amp;gt; 0 &amp;amp;&amp;amp; src[y][x - 1] === &#039;B&#039;;&lt;br /&gt;
                if (tb || rb) g[y][x] = &#039;H&#039;;&lt;br /&gt;
                else if (bb || lb) g[y][x] = &#039;D&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return g;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildReverseButtonPixels(BTN_W, SURFACE_H, slant, phase) {&lt;br /&gt;
        var H = 1 + SURFACE_H + 1;&lt;br /&gt;
        var W = BTN_W + 2;&lt;br /&gt;
        function io(x, y) {&lt;br /&gt;
            if (y &amp;lt; 0 || y &amp;gt;= H) return false;&lt;br /&gt;
            var ys = y - 1;&lt;br /&gt;
            var ysc = ys &amp;lt; 0 ? 0 : (ys &amp;gt;= SURFACE_H ? SURFACE_H - 1 : ys);&lt;br /&gt;
            var reversed = SURFACE_H - 1 - ysc;&lt;br /&gt;
            var m = buttonMask(BTN_W, reversed, slant, phase || 0);&lt;br /&gt;
            return x &amp;gt;= m.left &amp;amp;&amp;amp; x &amp;lt;= m.right;&lt;br /&gt;
        }&lt;br /&gt;
        var g = [];&lt;br /&gt;
        var src;&lt;br /&gt;
        var y;&lt;br /&gt;
        var x;&lt;br /&gt;
        for (y = 0; y &amp;lt; H; y++) {&lt;br /&gt;
            g[y] = [];&lt;br /&gt;
            for (x = 0; x &amp;lt; W; x++) {&lt;br /&gt;
                if (!io(x, y)) {&lt;br /&gt;
                    g[y][x] = &#039;BG&#039;;&lt;br /&gt;
                    continue;&lt;br /&gt;
                }&lt;br /&gt;
                var edge = !(io(x, y - 1) &amp;amp;&amp;amp; io(x, y + 1) &amp;amp;&amp;amp; io(x - 1, y) &amp;amp;&amp;amp; io(x + 1, y));&lt;br /&gt;
                g[y][x] = edge ? &#039;B&#039; : &#039;S&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        src = g.map(function (row) { return row.slice(); });&lt;br /&gt;
        for (y = 0; y &amp;lt; H; y++) {&lt;br /&gt;
            for (x = 0; x &amp;lt; W; x++) {&lt;br /&gt;
                if (src[y][x] !== &#039;S&#039;) continue;&lt;br /&gt;
                var tb = y &amp;gt; 0 &amp;amp;&amp;amp; src[y - 1][x] === &#039;B&#039;;&lt;br /&gt;
                var rb = x &amp;lt; W - 1 &amp;amp;&amp;amp; src[y][x + 1] === &#039;B&#039;;&lt;br /&gt;
                var bb = y &amp;lt; H - 1 &amp;amp;&amp;amp; src[y + 1][x] === &#039;B&#039;;&lt;br /&gt;
                var lb = x &amp;gt; 0 &amp;amp;&amp;amp; src[y][x - 1] === &#039;B&#039;;&lt;br /&gt;
                if (tb || rb) g[y][x] = &#039;H&#039;;&lt;br /&gt;
                else if (bb || lb) g[y][x] = &#039;D&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return g;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createImage(ctx, width, height, fillCode) {&lt;br /&gt;
        var image = ctx.createImageData(width, height);&lt;br /&gt;
        var color = COL[fillCode || &#039;BG&#039;];&lt;br /&gt;
        var i;&lt;br /&gt;
        for (i = 0; i &amp;lt; image.data.length; i += 4) {&lt;br /&gt;
            image.data[i] = color[0];&lt;br /&gt;
            image.data[i + 1] = color[1];&lt;br /&gt;
            image.data[i + 2] = color[2];&lt;br /&gt;
            image.data[i + 3] = 255;&lt;br /&gt;
        }&lt;br /&gt;
        return image;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setPixel(image, width, height, x, y, code) {&lt;br /&gt;
        var color;&lt;br /&gt;
        var index;&lt;br /&gt;
        x = integer(x);&lt;br /&gt;
        y = integer(y);&lt;br /&gt;
        if (x &amp;lt; 0 || x &amp;gt;= width || y &amp;lt; 0 || y &amp;gt;= height) return;&lt;br /&gt;
        color = COL[code] || COL.BG;&lt;br /&gt;
        index = (y * width + x) * 4;&lt;br /&gt;
        image.data[index] = color[0];&lt;br /&gt;
        image.data[index + 1] = color[1];&lt;br /&gt;
        image.data[index + 2] = color[2];&lt;br /&gt;
        image.data[index + 3] = 255;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fillRect(image, width, height, x, y, rectWidth, rectHeight, code) {&lt;br /&gt;
        var endX = Math.min(width, integer(x + rectWidth));&lt;br /&gt;
        var endY = Math.min(height, integer(y + rectHeight));&lt;br /&gt;
        var yy;&lt;br /&gt;
        var xx;&lt;br /&gt;
        x = Math.max(0, integer(x));&lt;br /&gt;
        y = Math.max(0, integer(y));&lt;br /&gt;
        for (yy = y; yy &amp;lt; endY; yy++) {&lt;br /&gt;
            for (xx = x; xx &amp;lt; endX; xx++) setPixel(image, width, height, xx, yy, code);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function paintGrid(image, width, height, grid, offsetX, offsetY, skipBackground) {&lt;br /&gt;
        var y;&lt;br /&gt;
        var x;&lt;br /&gt;
        offsetX = integer(offsetX);&lt;br /&gt;
        offsetY = integer(offsetY);&lt;br /&gt;
        for (y = 0; y &amp;lt; grid.length; y++) {&lt;br /&gt;
            for (x = 0; x &amp;lt; grid[y].length; x++) {&lt;br /&gt;
                if (skipBackground &amp;amp;&amp;amp; grid[y][x] === &#039;BG&#039;) continue;&lt;br /&gt;
                setPixel(image, width, height, offsetX + x, offsetY + y, grid[y][x]);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function paintRaisedRect(image, width, height, x, y, rectWidth, rectHeight) {&lt;br /&gt;
        var right = x + rectWidth - 1;&lt;br /&gt;
        var bottom = y + rectHeight - 1;&lt;br /&gt;
        var i;&lt;br /&gt;
        fillRect(image, width, height, x, y, rectWidth, rectHeight, &#039;B&#039;);&lt;br /&gt;
        fillRect(image, width, height, x + 1, y + 1, rectWidth - 2, rectHeight - 2, &#039;S&#039;);&lt;br /&gt;
        for (i = x + 1; i &amp;lt;= right - 1; i++) setPixel(image, width, height, i, bottom - 1, &#039;D&#039;);&lt;br /&gt;
        for (i = y + 1; i &amp;lt;= bottom - 1; i++) setPixel(image, width, height, x + 1, i, &#039;D&#039;);&lt;br /&gt;
        for (i = x + 1; i &amp;lt;= right - 1; i++) setPixel(image, width, height, i, y + 1, &#039;H&#039;);&lt;br /&gt;
        for (i = y + 1; i &amp;lt;= bottom - 1; i++) setPixel(image, width, height, right - 1, i, &#039;H&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function paintStandardFrame(image, width, height) {&lt;br /&gt;
        paintRaisedRect(image, width, height, 0, 0, width, height);&lt;br /&gt;
        fillRect(&lt;br /&gt;
            image,&lt;br /&gt;
            width,&lt;br /&gt;
            height,&lt;br /&gt;
            DIMENSIONS.WELL_TOP,&lt;br /&gt;
            DIMENSIONS.WELL_TOP,&lt;br /&gt;
            Math.max(0, width - DIMENSIONS.WELL_TOP * 2),&lt;br /&gt;
            DIMENSIONS.WELL2_BOTTOM - DIMENSIONS.WELL_TOP,&lt;br /&gt;
            &#039;BG&#039;&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createCanvas(className, width, height) {&lt;br /&gt;
        var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.className = className || &#039;portal-frame-canvas&#039;;&lt;br /&gt;
        canvas.width = Math.max(1, integer(width));&lt;br /&gt;
        canvas.height = Math.max(1, integer(height));&lt;br /&gt;
        canvas.style.width = canvas.width + &#039;px&#039;;&lt;br /&gt;
        canvas.style.height = canvas.height + &#039;px&#039;;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        return canvas;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawLabel(ctx, text, x, y, hover, fontSize) {&lt;br /&gt;
        var family = &#039;Galmuri11, sans-serif&#039;;&lt;br /&gt;
        try {&lt;br /&gt;
            family = w.getComputedStyle(ctx.canvas).fontFamily || family;&lt;br /&gt;
        } catch (error) {}&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.font = &#039;700 &#039; + integer(fontSize || 12) + &#039;px &#039; + family;&lt;br /&gt;
        ctx.fillStyle = hover ? FRAME.COLORS.label.replace(&#039;e2e2e2&#039;, &#039;ffffff&#039;) : FRAME.COLORS.label;&lt;br /&gt;
        if (hover) ctx.fillStyle = &#039;#ffffff&#039;;&lt;br /&gt;
        ctx.textAlign = &#039;center&#039;;&lt;br /&gt;
        ctx.textBaseline = &#039;middle&#039;;&lt;br /&gt;
        ctx.fillText(String(text || &#039;&#039;), integer(x), integer(y));&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setSeams(next) {&lt;br /&gt;
        var frame = w.CLBI.frame;&lt;br /&gt;
        var copy = {};&lt;br /&gt;
        Object.keys(next || {}).forEach(function (key) { copy[key] = next[key]; });&lt;br /&gt;
        copy.revision = integer((frame.seams &amp;amp;&amp;amp; frame.seams.revision) || 0) + 1;&lt;br /&gt;
        frame.seams = copy;&lt;br /&gt;
        seamListeners.slice().forEach(function (listener) {&lt;br /&gt;
            try { listener(copy); } catch (error) {}&lt;br /&gt;
        });&lt;br /&gt;
        try {&lt;br /&gt;
            document.dispatchEvent(new CustomEvent(&#039;framegeometryseams&#039;, { detail:copy }));&lt;br /&gt;
        } catch (error) {}&lt;br /&gt;
        if (w.mw &amp;amp;&amp;amp; w.mw.hook) w.mw.hook(&#039;frame.geometry.seams&#039;).fire(copy);&lt;br /&gt;
        return copy;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function onSeams(listener) {&lt;br /&gt;
        if (typeof listener !== &#039;function&#039;) return function () {};&lt;br /&gt;
        seamListeners.push(listener);&lt;br /&gt;
        if (w.CLBI.frame.seams &amp;amp;&amp;amp; w.CLBI.frame.seams.width) listener(w.CLBI.frame.seams);&lt;br /&gt;
        return function () {&lt;br /&gt;
            var index = seamListeners.indexOf(listener);&lt;br /&gt;
            if (index !== -1) seamListeners.splice(index, 1);&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    w.CLBI = w.CLBI || {};&lt;br /&gt;
    w.CLBI.frame = {&lt;br /&gt;
        FRAME:FRAME,&lt;br /&gt;
        DIMENSIONS:DIMENSIONS,&lt;br /&gt;
        COL:COL,&lt;br /&gt;
        integer:integer,&lt;br /&gt;
        snapWidth:snapWidth,&lt;br /&gt;
        readWidth:readWidth,&lt;br /&gt;
        readRenderWidth:readRenderWidth,&lt;br /&gt;
        getShellScale:getShellScale,&lt;br /&gt;
        setShellScale:setShellScale,&lt;br /&gt;
        onScale:onScale,&lt;br /&gt;
        applyCounterScale:applyCounterScale,&lt;br /&gt;
        buttonMask:buttonMask,&lt;br /&gt;
        buildButtonPixels:buildButtonPixels,&lt;br /&gt;
        buildReverseButtonPixels:buildReverseButtonPixels,&lt;br /&gt;
        createImage:createImage,&lt;br /&gt;
        setPixel:setPixel,&lt;br /&gt;
        fillRect:fillRect,&lt;br /&gt;
        paintGrid:paintGrid,&lt;br /&gt;
        paintRaisedRect:paintRaisedRect,&lt;br /&gt;
        paintStandardFrame:paintStandardFrame,&lt;br /&gt;
        createCanvas:createCanvas,&lt;br /&gt;
        drawLabel:drawLabel,&lt;br /&gt;
        setSeams:setSeams,&lt;br /&gt;
        onSeams:onSeams,&lt;br /&gt;
        seams:{}&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    setShellScale(readCssShellScale());&lt;br /&gt;
}(window, document));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4692</id>
		<title>미디어위키:Common.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4692"/>
		<updated>2026-07-17T09:49:33Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-fixed / js/Common.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;mw.loader.load(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* Raw MediaWiki script cache key.&lt;br /&gt;
   This is not a semantic version. It only prevents stale raw JS during active editing. */&lt;br /&gt;
window.CLBI_RAW_LOAD_BUST = &#039;portal-frame-canvas-pixel-20260717-001&#039;;&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Raw script loads are tracked so the site boot gate can wait for real subsystem scripts&lt;br /&gt;
before releasing the normal surface.  The old prefixed load-bust variable remains for&lt;br /&gt;
compatibility only; new loader state uses unprefixed EntryScriptLoads/EntryRawScriptPromises.&lt;br /&gt;
*/&lt;br /&gt;
window.EntryScriptLoads = window.EntryScriptLoads || [];&lt;br /&gt;
window.EntryRawScriptPromises = window.EntryRawScriptPromises || {};&lt;br /&gt;
function buildEntryRawScriptUrl(key) {&lt;br /&gt;
    var url = &#039;/index.php?title=&#039; + encodeURIComponent(key) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;;&lt;br /&gt;
    var token;&lt;br /&gt;
    var sep;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, key);&lt;br /&gt;
        }&lt;br /&gt;
        if (/[?&amp;amp;]_entryRev=/.test(url)) return url;&lt;br /&gt;
        token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(&#039;raw-script-fallback&#039;) : window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        token = window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
    return url + sep + &#039;v=&#039; + encodeURIComponent(String(token || &#039;entry-raw&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadClbiRawScript(title) {&lt;br /&gt;
    var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
    var promise;&lt;br /&gt;
&lt;br /&gt;
    if (!key) return Promise.resolve(false);&lt;br /&gt;
    if (window.EntryRawScriptPromises[key]) return window.EntryRawScriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
    promise = Promise.resolve()&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.ensureLoaded === &#039;function&#039;) {&lt;br /&gt;
                return window.RevisionManifest.ensureLoaded();&lt;br /&gt;
            }&lt;br /&gt;
            return null;&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function () { return null; })&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
                script.src = buildEntryRawScriptUrl(key);&lt;br /&gt;
                script.async = true;&lt;br /&gt;
                script.setAttribute(&#039;data-entry-raw-script&#039;, key);&lt;br /&gt;
                script.onload = function () { resolve({ title: key, ok: true, src: script.src }); };&lt;br /&gt;
                script.onerror = function () { resolve({ title: key, ok: false, src: script.src }); };&lt;br /&gt;
                (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    window.EntryRawScriptPromises[key] = promise;&lt;br /&gt;
    window.EntryScriptLoads.push(promise);&lt;br /&gt;
    return promise;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Main-page fresh assets with fallback&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css의 MainPage.css import는 안전망으로 그대로 유지한다.&lt;br /&gt;
대문에서는 새로고침마다 timestamp가 붙은 CSS/JS를 추가로 불러와&lt;br /&gt;
브라우저·프록시의 오래된 raw 응답을 우회한다.&lt;br /&gt;
&lt;br /&gt;
이 보조 로더가 실패해도 기존 정적 import와 기존 화면은 남는다.&lt;br /&gt;
*/&lt;br /&gt;
window.MainPageFreshAssets = window.MainPageFreshAssets || (function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var sessionToken = String(Date.now());&lt;br /&gt;
    var ASSET_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var scriptPromises = {};&lt;br /&gt;
    var styleNode = null;&lt;br /&gt;
&lt;br /&gt;
    function isMainPage() {&lt;br /&gt;
        var page = &#039;&#039;;&lt;br /&gt;
        try {&lt;br /&gt;
            page = String(mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;);&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return page.replace(/_/g, &#039; &#039;) === &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrl(title, type) {&lt;br /&gt;
        var ctype = type === &#039;style&#039; ? &#039;text/css&#039; : &#039;text/javascript&#039;;&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) +&lt;br /&gt;
            &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype) +&lt;br /&gt;
            &#039;&amp;amp;_mainPageFresh=&#039; + encodeURIComponent(sessionToken);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadStyle() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        if (styleNode &amp;amp;&amp;amp; styleNode.parentNode) {&lt;br /&gt;
            return Promise.resolve(true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var link = document.createElement(&#039;link&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            link.rel = &#039;stylesheet&#039;;&lt;br /&gt;
            link.href = rawUrl(&#039;MediaWiki:MainPage.css&#039;, &#039;style&#039;);&lt;br /&gt;
            link.setAttribute(&#039;data-main-page-fresh-style&#039;, sessionToken);&lt;br /&gt;
            link.onload = function () {&lt;br /&gt;
                styleNode = link;&lt;br /&gt;
                settle(true);&lt;br /&gt;
            };&lt;br /&gt;
            link.onerror = function () {&lt;br /&gt;
                /* Common.css의 정적 import가 그대로 남아 있으므로 화면은 유지된다. */&lt;br /&gt;
                settle(false);&lt;br /&gt;
            };&lt;br /&gt;
            timeout = window.setTimeout(function () { settle(false); }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(link);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadScript(title) {&lt;br /&gt;
        var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!key || !isMainPage()) return Promise.resolve(false);&lt;br /&gt;
        if (scriptPromises[key]) return scriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
        scriptPromises[key] = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            script.src = rawUrl(key, &#039;script&#039;);&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-main-page-fresh-script&#039;, key);&lt;br /&gt;
            script.onload = function () { settle(true); };&lt;br /&gt;
            script.onerror = function () { settle(false); };&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return scriptPromises[key];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensure() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        return loadStyle()&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:FrameGeometry.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryPillar.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:PortalSectionNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:BottomGuideNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (window.CategoryNav &amp;amp;&amp;amp; typeof window.CategoryNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.CategoryPillar &amp;amp;&amp;amp; typeof window.CategoryPillar.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryPillar.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.PortalSectionNav &amp;amp;&amp;amp; typeof window.PortalSectionNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.PortalSectionNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.render === &#039;function&#039;) {&lt;br /&gt;
                    window.BottomGuideNav.render();&lt;br /&gt;
                }&lt;br /&gt;
                return true;&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function status() {&lt;br /&gt;
        return {&lt;br /&gt;
            mainPage: isMainPage(),&lt;br /&gt;
            token: sessionToken,&lt;br /&gt;
            style: !!document.querySelector(&#039;link[data-main-page-fresh-style]&#039;),&lt;br /&gt;
            geometry: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:FrameGeometry.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            category: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            pillar: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryPillar.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            section: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:PortalSectionNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            guide: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:BottomGuideNav.js&amp;quot;]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            ensure();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(ensure, 0);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        ensure: ensure,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Account access page marker&lt;br /&gt;
   =========================================&lt;br /&gt;
   New shared systems use unprefixed names.  The server body class differs&lt;br /&gt;
   between skins/locales, so the canonical special-page name is normalized&lt;br /&gt;
   once and exposed as a stable page-state class for the login surface.&lt;br /&gt;
*/&lt;br /&gt;
(function markAccountLoginPage(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var canonical = &#039;&#039;;&lt;br /&gt;
    var pageName = &#039;&#039;;&lt;br /&gt;
    var isLoginPage = false;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        canonical = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039; : &#039;&#039;).toLowerCase();&lt;br /&gt;
        pageName = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    isLoginPage = canonical === &#039;userlogin&#039; || /^(?:special|특수):(?:userlogin|로그인)$/.test(pageName);&lt;br /&gt;
    if (!isLoginPage) return;&lt;br /&gt;
&lt;br /&gt;
    document.documentElement.classList.add(&#039;account-login-page-root&#039;);&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountTarget(link) {&lt;br /&gt;
        var href = &#039;&#039;;&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var text = &#039;&#039;;&lt;br /&gt;
        var sample = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!link) return false;&lt;br /&gt;
        href = String(link.getAttribute(&#039;href&#039;) || &#039;&#039;);&lt;br /&gt;
        title = String(link.getAttribute(&#039;title&#039;) || &#039;&#039;);&lt;br /&gt;
        text = String(link.textContent || &#039;&#039;);&lt;br /&gt;
        try { href = decodeURIComponent(href); } catch (err) {}&lt;br /&gt;
        sample = (href + &#039; &#039; + title + &#039; &#039; + text).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        return /(?:special|특수)\s*[:%]\s*(?:createaccount|계정\s*(?:만들기|생성))|createaccount|계정\s*(?:만들기|생성)/i.test(sample);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function suppressCreateAccountSurface(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var fixedSelectors = [&lt;br /&gt;
            &#039;.mw-createacct-benefits-container&#039;,&lt;br /&gt;
            &#039;.mw-createacct-benefits-list&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-cta&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;#mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;.mw-userlogin-create&#039;&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        fixedSelectors.forEach(function (selector) {&lt;br /&gt;
            Array.prototype.forEach.call(scope.querySelectorAll(selector), function (node) {&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                node.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;a[href]&#039;), function (link) {&lt;br /&gt;
            var container;&lt;br /&gt;
            if (!isCreateAccountTarget(link)) return;&lt;br /&gt;
&lt;br /&gt;
            link.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            link.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            link.setAttribute(&#039;tabindex&#039;, &#039;-1&#039;);&lt;br /&gt;
&lt;br /&gt;
            container = link.closest(&#039;.mw-ui-vform-field, .oo-ui-fieldLayout, .mw-userlogin-create, p, li&#039;);&lt;br /&gt;
            if (container &amp;amp;&amp;amp; container.querySelectorAll(&#039;a&#039;).length === 1) {&lt;br /&gt;
                container.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                container.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyMarker() {&lt;br /&gt;
        if (!document.body) return;&lt;br /&gt;
        document.body.classList.add(&#039;account-login-page&#039;);&lt;br /&gt;
        suppressCreateAccountSurface(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, applyMarker, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyMarker();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                suppressCreateAccountSurface(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Boot gate prelude&lt;br /&gt;
   =========================================&lt;br /&gt;
   This tiny prelude runs before the full EntryStore/EntryLoader/BootGate implementation.&lt;br /&gt;
   Its job is only to guarantee that the user sees the separate loading surface before the&lt;br /&gt;
   normal wiki shell can paint.  The full BootGate later adopts the same DOM node and keeps&lt;br /&gt;
   it open until the real full-entry readiness contract is satisfied.&lt;br /&gt;
&lt;br /&gt;
   Maintenance rule:&lt;br /&gt;
   - Do not move first-screen hiding into a late subsystem callback.  If the boot gate is&lt;br /&gt;
     meant to protect the first impression, the surface must be activated before sidebars,&lt;br /&gt;
     navbars, document wells, nations panels, or other entry surfaces can visibly fill in.&lt;br /&gt;
   - New names remain unprefixed.  Existing prefixed names elsewhere are legacy aliases only.&lt;br /&gt;
*/&lt;br /&gt;
(function installBootGatePrelude(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SCREEN_ID = &#039;boot-gate-screen&#039;;&lt;br /&gt;
    var STYLE_ID = &#039;boot-gate-prelude-style&#039;;&lt;br /&gt;
    var START_TIME = Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getMwConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.mw &amp;amp;&amp;amp; window.mw.config &amp;amp;&amp;amp; typeof window.mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = window.mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDeveloperOrEditingPage() {&lt;br /&gt;
        var ns = Number(getMwConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(getMwConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(getMwConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Developer/system pages must stay practical.  Editing MediaWiki:, File:,&lt;br /&gt;
         * Project:, Template:, Module:, Category:, or any non-view action already forces&lt;br /&gt;
         * a full page load in MediaWiki, so showing the entry boot screen there only slows&lt;br /&gt;
         * maintenance work and does not improve the public first impression.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = getMwConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(getMwConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(getMwConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var anonymousUser = isAnonymousUser();&lt;br /&gt;
    var disabled = isAuthenticationPage() || (!anonymousUser &amp;amp;&amp;amp; (hasQueryFlag(&#039;bootGate&#039;, &#039;0&#039;) || isDeveloperOrEditingPage()));&lt;br /&gt;
&lt;br /&gt;
    function injectStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (disabled || document.getElementById(STYLE_ID)) return;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = STYLE_ID;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active,body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt;:not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen{position:fixed!important;inset:0!important;z-index:2147483000!important;display:flex!important;align-items:center!important;justify-content:center!important;background:#080808!important;color:#d8d8d8!important;opacity:1;pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-decoration-layer{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-panel{position:relative;z-index:2;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-close{display:none;position:absolute;right:7px;top:6px;z-index:3;height:18px;min-width:18px;border:1px solid #000;background:#141414;color:#ddd;font-size:11px;line-height:16px;padding:0 5px;cursor:pointer;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview{inset:auto!important;left:18px!important;top:18px!important;width:min(720px,calc(100vw - 380px))!important;height:360px!important;z-index:99990!important;overflow:hidden!important;border:1px solid #000!important;box-shadow:0 10px 28px rgba(0,0,0,.55)!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-panel,#boot-gate-screen.is-preview .boot-gate-close{pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-close{display:block;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activate() {&lt;br /&gt;
        if (disabled) return;&lt;br /&gt;
        injectStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeNode() {&lt;br /&gt;
        var node;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var status;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var fill;&lt;br /&gt;
        var progress;&lt;br /&gt;
        var detail;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
&lt;br /&gt;
        if (disabled || !document.body) return null;&lt;br /&gt;
        activate();&lt;br /&gt;
&lt;br /&gt;
        node = document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) return node;&lt;br /&gt;
&lt;br /&gt;
        node = document.createElement(&#039;div&#039;);&lt;br /&gt;
        node.id = SCREEN_ID;&lt;br /&gt;
        node.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
        node.setAttribute(&#039;data-boot-gate-prelude&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        title.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        status = document.createElement(&#039;div&#039;);&lt;br /&gt;
        status.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        status.textContent = &#039;Preparing site entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        fill = document.createElement(&#039;div&#039;);&lt;br /&gt;
        fill.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(fill);&lt;br /&gt;
&lt;br /&gt;
        progress = document.createElement(&#039;div&#039;);&lt;br /&gt;
        progress.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        progress.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        detail = document.createElement(&#039;div&#039;);&lt;br /&gt;
        detail.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        detail.textContent = &#039;boot gate prelude&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { release(node); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(title);&lt;br /&gt;
        panel.appendChild(status);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(progress);&lt;br /&gt;
        panel.appendChild(detail);&lt;br /&gt;
        node.appendChild(decoLayer);&lt;br /&gt;
        node.appendChild(panel);&lt;br /&gt;
        node.appendChild(close);&lt;br /&gt;
&lt;br /&gt;
        document.body.insertBefore(node, document.body.firstChild || null);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function onBody(callback) {&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            callback();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, callback, { once: true });&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function tick() {&lt;br /&gt;
            if (document.body) return callback();&lt;br /&gt;
            window.setTimeout(tick, 10);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function release(node) {&lt;br /&gt;
        node = node || document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) {&lt;br /&gt;
            node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
            node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            }, 240);&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (document.documentElement) document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    activate();&lt;br /&gt;
    onBody(function () {&lt;br /&gt;
        activate();&lt;br /&gt;
        makeNode();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.__BootGatePrelude = {&lt;br /&gt;
        startTime: START_TIME,&lt;br /&gt;
        activate: activate,&lt;br /&gt;
        ensure: makeNode,&lt;br /&gt;
        release: release,&lt;br /&gt;
        node: function () { return document.getElementById(SCREEN_ID); },&lt;br /&gt;
        disabled: disabled&lt;br /&gt;
    };&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Site boot gate and entry artifact contract&lt;br /&gt;
   =========================================&lt;br /&gt;
   This is the initial-load full/half entry artifact system.&lt;br /&gt;
&lt;br /&gt;
   Purpose:&lt;br /&gt;
   - The loading screen is not decorative. It exists only during the first site entry in a&lt;br /&gt;
     tab, before the user is allowed into the normal wiki surface.&lt;br /&gt;
   - A &amp;quot;full&amp;quot; entry must mean that the first view of that system can appear without an&lt;br /&gt;
     additional visible data load. For the nations system, the 1950 entry is full only when&lt;br /&gt;
     its nation list/link-map data and first-view pixel decorations are ready in this tab.&lt;br /&gt;
   - A &amp;quot;half&amp;quot; entry is a predictive warm state for the next likely path. It may fetch and&lt;br /&gt;
     parse data, but may skip expensive final work such as image/canvas preparation until it&lt;br /&gt;
     is promoted to full.&lt;br /&gt;
   - This principle is a site-wide design priority, like SPA continuity. New viewers,&lt;br /&gt;
     document systems, and information panels should define their entry full/half packs&lt;br /&gt;
     before exposing a first screen that can visibly fill in later.&lt;br /&gt;
   - SPA navigation is a consumer phase, not a blocking phase.  Do not add BootGate holds&lt;br /&gt;
     to SPA route changes.  If a route needs seamless first paint, prepare its artifacts&lt;br /&gt;
     during the initial boot pack and have the subsystem consume EntryStore synchronously&lt;br /&gt;
     before inserting or painting visible DOM.&lt;br /&gt;
&lt;br /&gt;
   Naming rule:&lt;br /&gt;
   - New public APIs, globals, classes, functions, files, and components must not use a&lt;br /&gt;
     project prefix. Old prefixed globals are legacy compatibility surfaces only.&lt;br /&gt;
   - New code should use names such as BootGate, EntryLoader, EntryStore, and boot-gate-*.&lt;br /&gt;
*/&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var MANIFEST_TITLE = &#039;MediaWiki:EntryManifest.json&#039;;&lt;br /&gt;
    var BUILD_ID = &#039;20260711-existing-account-login-001&#039;;&lt;br /&gt;
    var deferredEntryWarmups = [];&lt;br /&gt;
    var READY_KEY = &#039;boot-gate-ready-version&#039;;&lt;br /&gt;
    var DISMISS_PARAM = &#039;bootGate&#039;;&lt;br /&gt;
    var bootStarted = false;&lt;br /&gt;
    var bootPromise = null;&lt;br /&gt;
    var bootNode = null;&lt;br /&gt;
    var bootStatusNode = null;&lt;br /&gt;
    var bootProgressNode = null;&lt;br /&gt;
    var bootDetailNode = null;&lt;br /&gt;
    var bootFillNode = null;&lt;br /&gt;
    var bootStartTime = 0;&lt;br /&gt;
    var earlyBootStyleInjected = false;&lt;br /&gt;
    var loginGateLocked = false;&lt;br /&gt;
    var loginGateActionNode = null;&lt;br /&gt;
&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.config &amp;amp;&amp;amp; typeof mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBootPageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        return canonical === &#039;createaccount&#039; || /^(?:special|특수):(?:createaccount|계정 ?(?:만들기|생성))/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = readConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(readConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requiresLoginGate() {&lt;br /&gt;
        return isAnonymousUser() &amp;amp;&amp;amp; !isAuthenticationPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBootExcludedPage() {&lt;br /&gt;
        var ns = Number(readConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(readConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(readConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * The public boot gate is for normal reading surfaces.  Developer/system&lt;br /&gt;
         * namespaces and edit/diff/history actions already reload outside SPA, so&lt;br /&gt;
         * blocking them would turn every maintenance save into another entry boot.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var LOGIN_REQUIRED = requiresLoginGate();&lt;br /&gt;
    var BOOT_EXCLUDED_PAGE = isBootExcludedPage() &amp;amp;&amp;amp; !LOGIN_REQUIRED;&lt;br /&gt;
&lt;br /&gt;
    function injectEarlyBootStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (earlyBootStyleInjected || !document.documentElement) return;&lt;br /&gt;
        earlyBootStyleInjected = true;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = &#039;boot-gate-early-style&#039;;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active, body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt; :not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBootSurface() {&lt;br /&gt;
        injectEarlyBootStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!BOOT_EXCLUDED_PAGE) activateBootSurface();&lt;br /&gt;
    else if (window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.release) window.__BootGatePrelude.release();&lt;br /&gt;
&lt;br /&gt;
    var defaultManifest = {&lt;br /&gt;
        version: &#039;20260710-globe-vhs-restore-entry-001&#039;,&lt;br /&gt;
        boot: {&lt;br /&gt;
            minDisplayMs: 950,&lt;br /&gt;
            cachedMinDisplayMs: 350,&lt;br /&gt;
            maxBlockingMs: 15000&lt;br /&gt;
        },&lt;br /&gt;
        initial: {&lt;br /&gt;
            full: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;decorations-registry&#039;,&lt;br /&gt;
                    label: &#039;DECORATION REGISTRY&#039;,&lt;br /&gt;
                    type: &#039;decorations&#039;,&lt;br /&gt;
                    ref: &#039;MediaWiki:Decorations.json&#039;,&lt;br /&gt;
                    page: &#039;시대&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    preparePixels: true&lt;br /&gt;
                },&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1950-entry&#039;,&lt;br /&gt;
                    label: &#039;1950 ERA ENTRY&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    level: &#039;full&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ],&lt;br /&gt;
            half: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1960-half&#039;,&lt;br /&gt;
                    label: &#039;1960 ERA HALF&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1960&#039;,&lt;br /&gt;
                    level: &#039;half&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ]&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function now() {&lt;br /&gt;
        return Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var BootPerf = window.BootPerf = window.BootPerf || (function () {&lt;br /&gt;
        var t0 = now();&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;entry&#039;), start: now(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = now();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = ended - item.start;&lt;br /&gt;
            item.offset = item.start - t0;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            entries.push(item);&lt;br /&gt;
            return item;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function instant(name, meta) {&lt;br /&gt;
            var t = now();&lt;br /&gt;
            entries.push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: t - t0, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measure(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function rows() {&lt;br /&gt;
            return entries.slice().sort(function (a, b) { return a.start - b.start; }).map(function (item) {&lt;br /&gt;
                return {&lt;br /&gt;
                    offset: item.offset,&lt;br /&gt;
                    ms: item.ms,&lt;br /&gt;
                    name: item.name,&lt;br /&gt;
                    meta: item.meta || {}&lt;br /&gt;
                };&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            var total = list.reduce(function (max, item) { return Math.max(max, item.offset + item.ms); }, 0);&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD_ID,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: total,&lt;br /&gt;
                entries: list,&lt;br /&gt;
                active: Object.keys(active).map(function (id) { return active[id]; })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function print() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            if (!window.console || !console.log) return summary();&lt;br /&gt;
            try {&lt;br /&gt;
                console.groupCollapsed(&#039;[BootPerf] entry loading timeline · &#039; + BUILD_ID);&lt;br /&gt;
                if (console.table) console.table(list.map(function (item) {&lt;br /&gt;
                    return {&lt;br /&gt;
                        offset: item.offset + &#039;ms&#039;,&lt;br /&gt;
                        duration: item.ms + &#039;ms&#039;,&lt;br /&gt;
                        name: item.name,&lt;br /&gt;
                        detail: JSON.stringify(item.meta || {})&lt;br /&gt;
                    };&lt;br /&gt;
                }));&lt;br /&gt;
                else list.forEach(function (item) { console.log(item.offset + &#039;ms&#039;, item.ms + &#039;ms&#039;, item.name, item.meta || {}); });&lt;br /&gt;
                console.groupEnd();&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            return summary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: instant,&lt;br /&gt;
            measure: measure,&lt;br /&gt;
            rows: rows,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            print: print&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var InteractionPerf = window.InteractionPerf = window.InteractionPerf || (function () {&lt;br /&gt;
        var BUILD = &#039;20260710-interaction-perf-instrument-001&#039;;&lt;br /&gt;
        var t0 = (window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now());&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
        var maxEntries = 1600;&lt;br /&gt;
&lt;br /&gt;
        function perfNow() {&lt;br /&gt;
            return window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function push(entry) {&lt;br /&gt;
            entries.push(entry);&lt;br /&gt;
            if (entries.length &amp;gt; maxEntries) entries.splice(0, entries.length - maxEntries);&lt;br /&gt;
            return entry;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;interaction&#039;), start: perfNow(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = perfNow();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = Math.round((ended - item.start) * 100) / 100;&lt;br /&gt;
            item.offset = Math.round((item.start - t0) * 100) / 100;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            return push(item);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function mark(name, meta) {&lt;br /&gt;
            var t = perfNow();&lt;br /&gt;
            return push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: Math.round((t - t0) * 100) / 100, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureSync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                var value = fn();&lt;br /&gt;
                end(id, { ok: true });&lt;br /&gt;
                return value;&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                throw err;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureAsync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: Math.round((perfNow() - t0) * 100) / 100,&lt;br /&gt;
                entries: entries.slice().sort(function (a, b) {&lt;br /&gt;
                    if (a.offset !== b.offset) return a.offset - b.offset;&lt;br /&gt;
                    return b.ms - a.ms;&lt;br /&gt;
                }),&lt;br /&gt;
                active: Object.keys(active).map(function (id) {&lt;br /&gt;
                    var item = active[id];&lt;br /&gt;
                    return { offset: Math.round((item.start - t0) * 100) / 100, ms: Math.round((perfNow() - item.start) * 100) / 100, name: item.name, meta: item.meta || {} };&lt;br /&gt;
                })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function table() {&lt;br /&gt;
            var data = summary().entries.map(function (entry) {&lt;br /&gt;
                return { offset: entry.offset, ms: entry.ms, name: entry.name, meta: entry.meta };&lt;br /&gt;
            });&lt;br /&gt;
            if (window.console &amp;amp;&amp;amp; console.table) console.table(data);&lt;br /&gt;
            return data;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.PerformanceObserver &amp;amp;&amp;amp; !window.CLBI_InteractionLongTaskObserverBound) {&lt;br /&gt;
                window.CLBI_InteractionLongTaskObserverBound = true;&lt;br /&gt;
                new PerformanceObserver(function (list) {&lt;br /&gt;
                    list.getEntries().forEach(function (entry) {&lt;br /&gt;
                        mark(&#039;browser long task&#039;, {&lt;br /&gt;
                            ms: Math.round(entry.duration * 100) / 100,&lt;br /&gt;
                            start: Math.round(entry.startTime * 100) / 100,&lt;br /&gt;
                            attribution: entry.attribution &amp;amp;&amp;amp; entry.attribution.length ? entry.attribution.length : 0&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }).observe({ entryTypes: [&#039;longtask&#039;] });&lt;br /&gt;
            }&lt;br /&gt;
        } catch (ignoreLongTaskObserver) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD,&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: mark,&lt;br /&gt;
            measureSync: measureSync,&lt;br /&gt;
            measureAsync: measureAsync,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            table: table&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
    function toArray(value) {&lt;br /&gt;
        return Array.prototype.slice.call(value || []);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function unique(list) {&lt;br /&gt;
        var seen = {};&lt;br /&gt;
        var out = [];&lt;br /&gt;
        (list || []).forEach(function (item) {&lt;br /&gt;
            item = String(item || &#039;&#039;).trim();&lt;br /&gt;
            if (!item || seen[item]) return;&lt;br /&gt;
            seen[item] = true;&lt;br /&gt;
            out.push(item);&lt;br /&gt;
        });&lt;br /&gt;
        return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasBootParam(value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + DISMISS_PARAM + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeTitle(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;#&#039;)[0]&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function extractTitleFromUrl(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;);&lt;br /&gt;
        var match = text.match(/[?&amp;amp;]title=([^&amp;amp;]+)/i);&lt;br /&gt;
        if (match) return normalizeTitle(decodeURIComponent(match[1].replace(/\+/g, &#039; &#039;)));&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeRefKey(ref) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        title = extractTitleFromUrl(text);&lt;br /&gt;
        if (title) return &#039;title:&#039; + title.toLowerCase();&lt;br /&gt;
        if (text.indexOf(&#039;/&#039;) === -1 &amp;amp;&amp;amp; text.indexOf(&#039;:&#039;) !== -1) return &#039;title:&#039; + normalizeTitle(text).toLowerCase();&lt;br /&gt;
        return &#039;url:&#039; + text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrlForRef(ref, ctype) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(text) || text.charAt(0) === &#039;/&#039;) return text;&lt;br /&gt;
        title = normalizeTitle(text.indexOf(&#039;:&#039;) !== -1 ? text : (&#039;MediaWiki:&#039; + text));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            return mw.util.getUrl(title, { action: &#039;raw&#039;, ctype: ctype || &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype || &#039;application/json&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getApiEndpoint() {&lt;br /&gt;
        return (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.wikiScript === &#039;function&#039;) ? mw.util.wikiScript(&#039;api&#039;) : &#039;/api.php&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchApi(params) {&lt;br /&gt;
        var body = new URLSearchParams();&lt;br /&gt;
        Object.keys(params || {}).forEach(function (key) {&lt;br /&gt;
            body.append(key, params[key]);&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(getApiEndpoint(), {&lt;br /&gt;
            method: &#039;POST&#039;,&lt;br /&gt;
            credentials: &#039;same-origin&#039;,&lt;br /&gt;
            headers: { &#039;Content-Type&#039;: &#039;application/x-www-form-urlencoded; charset=UTF-8&#039; },&lt;br /&gt;
            body: body.toString()&lt;br /&gt;
        }).then(function (res) {&lt;br /&gt;
            if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
            return res.json();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =========================================&lt;br /&gt;
       Revision manifest and persistent entry cache&lt;br /&gt;
       =========================================&lt;br /&gt;
       The first boot still prepares current-tab artifacts, but freshness is not guessed from&lt;br /&gt;
       filenames or old localStorage flags.  The client reads a tiny server-side current-state&lt;br /&gt;
       manifest, compares page revisions / file sha1 values, and only keeps cached raw resources&lt;br /&gt;
       whose revision token still matches the server.  The manifest is a latest-state table, not&lt;br /&gt;
       an append-only client log.&lt;br /&gt;
    */&lt;br /&gt;
    var REVISION_MANIFEST_ACTION = &#039;entryrevisionmanifest&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_LOCAL_KEY = &#039;entry-revision-manifest-current-v1&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_PRUNE_LOCAL_KEY = &#039;entry-revision-manifest-last-client-prune-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_NAME = &#039;entry-cache-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_INDEX_KEY = &#039;entry-cache-index-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_REQUEST_PREFIX = &#039;/__entry-cache__/&#039;;&lt;br /&gt;
    var ENTRY_CACHE_PACK_KEY = &#039;entry-cache-pack-state-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
    function hasCacheStorage() {&lt;br /&gt;
        return !!(window.caches &amp;amp;&amp;amp; typeof window.caches.open === &#039;function&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function safeJsonParse(text, fallback) {&lt;br /&gt;
        try { return JSON.parse(text); } catch (err) { return fallback; }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readLocalJson(key, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            var text = window.localStorage ? window.localStorage.getItem(key) : null;&lt;br /&gt;
            return text ? safeJsonParse(text, fallback) : fallback;&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return fallback;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function writeLocalJson(key, value) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.localStorage) window.localStorage.setItem(key, JSON.stringify(value));&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeManifestTitle(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var match;&lt;br /&gt;
        var i;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        for (i = 0; i &amp;lt; 3; i += 1) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
            } catch (err) { break; }&lt;br /&gt;
        }&lt;br /&gt;
        match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
        if (match) text = match[1];&lt;br /&gt;
        text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^\/+/, &#039;&#039;)&lt;br /&gt;
            .replace(/^index\.php\/?/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^wiki\/?/i, &#039;&#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
        match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/([^?#]+)(?:[?#].*)?$/i);&lt;br /&gt;
        if (match) text = &#039;File:&#039; + match[1];&lt;br /&gt;
        text = text.split(&#039;#&#039;)[0].replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
        text = text.replace(/^(?:파일|Image|이미지)\s*:/i, &#039;File:&#039;);&lt;br /&gt;
        if (/^(?:mediawiki|template|module|manage|file)\s*:/i.test(text)) {&lt;br /&gt;
            text = text.replace(/^([^:]+)\s*:\s*/, function (_, ns) { return ns.charAt(0).toUpperCase() + ns.slice(1).toLowerCase() + &#039;:&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
        if (/^File:/i.test(text)) text = &#039;File:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        if (/^Mediawiki:/i.test(text)) text = &#039;MediaWiki:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        return text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resourceToken(resource) {&lt;br /&gt;
        if (!resource || typeof resource !== &#039;object&#039;) return &#039;&#039;;&lt;br /&gt;
        return String(resource.revision || resource.sha1 || resource.hash || resource.timestamp || resource.updatedAt || resource.url || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createEntryCache() {&lt;br /&gt;
        var index = readLocalJson(ENTRY_CACHE_INDEX_KEY, { entries: {} }) || { entries: {} };&lt;br /&gt;
        var packs = readLocalJson(ENTRY_CACHE_PACK_KEY, { packs: {} }) || { packs: {} };&lt;br /&gt;
        var objectUrls = {};&lt;br /&gt;
        var stats = {&lt;br /&gt;
            textHits: 0,&lt;br /&gt;
            blobHits: 0,&lt;br /&gt;
            misses: 0,&lt;br /&gt;
            networkStores: 0,&lt;br /&gt;
            stores: 0,&lt;br /&gt;
            deletes: 0&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        function ensureIndex() {&lt;br /&gt;
            if (!index || typeof index !== &#039;object&#039;) index = { entries: {} };&lt;br /&gt;
            if (!index.entries || typeof index.entries !== &#039;object&#039;) index.entries = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactIndexForLocalStorage() {&lt;br /&gt;
            var entries;&lt;br /&gt;
            var next = {};&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var entry = entries[key] || {};&lt;br /&gt;
                var kind = String(entry.kind || &#039;raw&#039;).toLowerCase();&lt;br /&gt;
                /*&lt;br /&gt;
                 * CacheStorage already owns image/blob bodies.  Keeping one localStorage&lt;br /&gt;
                 * metadata row per flag/blob competes with MediaWiki ResourceLoader&#039;s own&lt;br /&gt;
                 * localStorage module store and can trigger QuotaExceededError before our&lt;br /&gt;
                 * code even runs on the next page load.  Text/json/file URL rows stay indexed&lt;br /&gt;
                 * because they are small and useful for invalidation diagnostics.&lt;br /&gt;
                 */&lt;br /&gt;
                if (kind === &#039;image&#039; || kind === &#039;blob&#039;) return;&lt;br /&gt;
                next[key] = entry;&lt;br /&gt;
            });&lt;br /&gt;
            index.entries = next;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensurePacks() {&lt;br /&gt;
            if (!packs || typeof packs !== &#039;object&#039;) packs = { packs: {} };&lt;br /&gt;
            if (!packs.packs || typeof packs.packs !== &#039;object&#039;) packs.packs = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function saveIndex() {&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            compactIndexForLocalStorage();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_INDEX_KEY, index);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function savePacks() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_PACK_KEY, packs);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function requestForKey(key) {&lt;br /&gt;
            return new Request(ENTRY_CACHE_REQUEST_PREFIX + encodeURIComponent(String(key || &#039;&#039;)), { credentials: &#039;same-origin&#039; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function openCache() {&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return window.caches.open(ENTRY_CACHE_NAME).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheEntry(key, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            index.entries[key] = Object.assign({}, index.entries[key] || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                resourceKey: String(meta &amp;amp;&amp;amp; meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                token: String(meta &amp;amp;&amp;amp; meta.token || &#039;&#039;),&lt;br /&gt;
                kind: String(meta &amp;amp;&amp;amp; meta.kind || &#039;raw&#039;),&lt;br /&gt;
                contentType: String(meta &amp;amp;&amp;amp; meta.contentType || &#039;&#039;),&lt;br /&gt;
                size: Number(meta &amp;amp;&amp;amp; meta.size || 0) || null,&lt;br /&gt;
                createdAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].createdAt ? index.entries[key].createdAt : now(),&lt;br /&gt;
                updatedAt: now(),&lt;br /&gt;
                lastHitAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].lastHitAt ? index.entries[key].lastHitAt : null,&lt;br /&gt;
                hits: Number(index.entries[key] &amp;amp;&amp;amp; index.entries[key].hits || 0)&lt;br /&gt;
            });&lt;br /&gt;
            saveIndex();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function markHit(key, field) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            if (index.entries[key]) {&lt;br /&gt;
                index.entries[key].hits = Number(index.entries[key].hits || 0) + 1;&lt;br /&gt;
                index.entries[key].lastHitAt = now();&lt;br /&gt;
                saveIndex();&lt;br /&gt;
            }&lt;br /&gt;
            if (field &amp;amp;&amp;amp; stats[field] != null) stats[field] += 1;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getResponse(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return null;&lt;br /&gt;
                return cache.match(requestForKey(key)).then(function (res) {&lt;br /&gt;
                    if (!res) {&lt;br /&gt;
                        stats.misses += 1;&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }&lt;br /&gt;
                    return res;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putResponse(key, response, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !response || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var cloned;&lt;br /&gt;
                var headers;&lt;br /&gt;
                var bodyPromise;&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                cloned = response.clone();&lt;br /&gt;
                bodyPromise = cloned.blob().catch(function () { return null; });&lt;br /&gt;
                return bodyPromise.then(function (blob) {&lt;br /&gt;
                    if (!blob) return false;&lt;br /&gt;
                    headers = new Headers(response.headers || {});&lt;br /&gt;
                    if (!headers.get(&#039;Content-Type&#039;)) headers.set(&#039;Content-Type&#039;, meta.contentType || blob.type || &#039;application/octet-stream&#039;);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cache-Key&#039;, key);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Resource-Key&#039;, String(meta.resourceKey || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Revision-Token&#039;, String(meta.token || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cached-At&#039;, String(now()));&lt;br /&gt;
                    return cache.put(requestForKey(key), new Response(blob, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                        stats.stores += 1;&lt;br /&gt;
                        cacheEntry(key, {&lt;br /&gt;
                            resourceKey: meta.resourceKey,&lt;br /&gt;
                            token: meta.token,&lt;br /&gt;
                            kind: meta.kind || &#039;blob&#039;,&lt;br /&gt;
                            contentType: headers.get(&#039;Content-Type&#039;) || blob.type || &#039;&#039;,&lt;br /&gt;
                            size: blob.size&lt;br /&gt;
                        });&lt;br /&gt;
                        return true;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getText(key) {&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return null;&lt;br /&gt;
                markHit(key, &#039;textHits&#039;);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putText(key, text, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var headers;&lt;br /&gt;
                var body = String(text || &#039;&#039;);&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                headers = new Headers({&lt;br /&gt;
                    &#039;Content-Type&#039;: meta.contentType || &#039;text/plain; charset=UTF-8&#039;,&lt;br /&gt;
                    &#039;X-Entry-Cache-Key&#039;: key,&lt;br /&gt;
                    &#039;X-Entry-Resource-Key&#039;: String(meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Revision-Token&#039;: String(meta.token || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Cached-At&#039;: String(now())&lt;br /&gt;
                });&lt;br /&gt;
                return cache.put(requestForKey(key), new Response(body, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                    stats.stores += 1;&lt;br /&gt;
                    cacheEntry(key, {&lt;br /&gt;
                        resourceKey: meta.resourceKey,&lt;br /&gt;
                        token: meta.token,&lt;br /&gt;
                        kind: meta.kind || &#039;text&#039;,&lt;br /&gt;
                        contentType: headers.get(&#039;Content-Type&#039;),&lt;br /&gt;
                        size: body.length&lt;br /&gt;
                    });&lt;br /&gt;
                    return true;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getBlobUrl(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                return Promise.resolve(objectUrls[key]);&lt;br /&gt;
            }&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return &#039;&#039;;&lt;br /&gt;
                return res.blob().then(function (blob) {&lt;br /&gt;
                    if (!blob || !blob.size) return &#039;&#039;;&lt;br /&gt;
                    objectUrls[key] = URL.createObjectURL(blob);&lt;br /&gt;
                    markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                    return objectUrls[key];&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchBlobUrl(url, key, meta, options) {&lt;br /&gt;
            url = String(url || &#039;&#039;).trim();&lt;br /&gt;
            key = String(key || &#039;&#039;).trim();&lt;br /&gt;
            if (!url || !key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            return getBlobUrl(key).then(function (cachedUrl) {&lt;br /&gt;
                if (cachedUrl) return cachedUrl;&lt;br /&gt;
                return fetch(url, {&lt;br /&gt;
                    credentials: &#039;same-origin&#039;,&lt;br /&gt;
                    cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
                }).then(function (res) {&lt;br /&gt;
                    if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                    stats.networkStores += 1;&lt;br /&gt;
                    return putResponse(key, res, Object.assign({}, meta || {}, { kind: meta &amp;amp;&amp;amp; meta.kind ? meta.kind : &#039;blob&#039; })).then(function () {&lt;br /&gt;
                        return getBlobUrl(key);&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return &#039;&#039;;&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function deleteKey(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
                delete objectUrls[key];&lt;br /&gt;
            }&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                return cache.delete(requestForKey(key)).then(function (ok) {&lt;br /&gt;
                    ensureIndex();&lt;br /&gt;
                    if (index.entries &amp;amp;&amp;amp; index.entries[key]) {&lt;br /&gt;
                        delete index.entries[key];&lt;br /&gt;
                        stats.deletes += 1;&lt;br /&gt;
                        saveIndex();&lt;br /&gt;
                    }&lt;br /&gt;
                    return ok;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function invalidateResources(resourceKeys) {&lt;br /&gt;
            var map = {};&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys = [];&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            (resourceKeys || []).forEach(function (resourceKey) {&lt;br /&gt;
                resourceKey = String(resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (resourceKey) map[resourceKey] = true;&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(entries).forEach(function (cacheKey) {&lt;br /&gt;
                var resourceKey = String(entries[cacheKey] &amp;amp;&amp;amp; entries[cacheKey].resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (map[resourceKey]) keys.push(cacheKey);&lt;br /&gt;
            });&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function prune(maxAgeDays) {&lt;br /&gt;
            var days = Math.max(1, Number(maxAgeDays) || 7);&lt;br /&gt;
            var cutoff = now() - days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys;&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            keys = Object.keys(entries).filter(function (key) {&lt;br /&gt;
                return Number(entries[key] &amp;amp;&amp;amp; entries[key].createdAt || 0) &amp;lt; cutoff;&lt;br /&gt;
            });&lt;br /&gt;
            window.localStorage &amp;amp;&amp;amp; window.localStorage.setItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY, String(now()));&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function reset() {&lt;br /&gt;
            Object.keys(objectUrls).forEach(function (key) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
            });&lt;br /&gt;
            objectUrls = {};&lt;br /&gt;
            index = { entries: {} };&lt;br /&gt;
            packs = { packs: {} };&lt;br /&gt;
            saveIndex();&lt;br /&gt;
            savePacks();&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            return window.caches.delete(ENTRY_CACHE_NAME).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packReady(key, token) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            return !!(packs.packs[key] &amp;amp;&amp;amp; String(packs.packs[key].token || &#039;&#039;) === token &amp;amp;&amp;amp; packs.packs[key].ready);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setPackReady(key, token, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            packs.packs[key] = Object.assign({}, meta || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                token: token,&lt;br /&gt;
                ready: true,&lt;br /&gt;
                updatedAt: now()&lt;br /&gt;
            });&lt;br /&gt;
            savePacks();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packInfo() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            return Object.assign({}, packs.packs || {});&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function info() {&lt;br /&gt;
            var lastClientPrune = 0;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var byKind = {};&lt;br /&gt;
            try { lastClientPrune = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var kind = String(entries[key] &amp;amp;&amp;amp; entries[key].kind || &#039;raw&#039;);&lt;br /&gt;
                byKind[kind] = (byKind[kind] || 0) + 1;&lt;br /&gt;
            });&lt;br /&gt;
            return {&lt;br /&gt;
                supported: hasCacheStorage(),&lt;br /&gt;
                name: ENTRY_CACHE_NAME,&lt;br /&gt;
                entries: Object.keys(entries).length,&lt;br /&gt;
                byKind: byKind,&lt;br /&gt;
                packs: Object.keys(packs.packs || {}).length,&lt;br /&gt;
                lastClientPruneAt: lastClientPrune || null,&lt;br /&gt;
                stats: Object.assign({}, stats)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Trim legacy image/blob metadata rows produced by older builds. */&lt;br /&gt;
        try { saveIndex(); } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            getResponse: getResponse,&lt;br /&gt;
            putResponse: putResponse,&lt;br /&gt;
            getText: getText,&lt;br /&gt;
            putText: putText,&lt;br /&gt;
            getBlobUrl: getBlobUrl,&lt;br /&gt;
            fetchBlobUrl: fetchBlobUrl,&lt;br /&gt;
            invalidateResources: invalidateResources,&lt;br /&gt;
            prune: prune,&lt;br /&gt;
            reset: reset,&lt;br /&gt;
            packReady: packReady,&lt;br /&gt;
            setPackReady: setPackReady,&lt;br /&gt;
            packInfo: packInfo,&lt;br /&gt;
            info: info&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryCache = window.EntryCache || createEntryCache();&lt;br /&gt;
&lt;br /&gt;
    function createRevisionManifestService() {&lt;br /&gt;
        var current = null;&lt;br /&gt;
        var previous = readLocalJson(REVISION_MANIFEST_LOCAL_KEY, null);&lt;br /&gt;
        var loadPromise = null;&lt;br /&gt;
        var changedResources = [];&lt;br /&gt;
&lt;br /&gt;
        function unwrap(payload) {&lt;br /&gt;
            return payload &amp;amp;&amp;amp; (payload.entryrevisionmanifest || payload.revisionManifest || payload) || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourcesOf(manifest) {&lt;br /&gt;
            return manifest &amp;amp;&amp;amp; manifest.resources &amp;amp;&amp;amp; typeof manifest.resources === &#039;object&#039; ? manifest.resources : {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactRevisionManifest(manifest) {&lt;br /&gt;
            var out;&lt;br /&gt;
            var resources;&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039;) return null;&lt;br /&gt;
            out = {&lt;br /&gt;
                manifestVersion: manifest.manifestVersion || manifest.version || &#039;&#039;,&lt;br /&gt;
                version: manifest.version || manifest.manifestVersion || &#039;&#039;,&lt;br /&gt;
                generatedAt: manifest.generatedAt || &#039;&#039;,&lt;br /&gt;
                pruneDays: manifest.pruneDays,&lt;br /&gt;
                clientPruneDays: manifest.clientPruneDays,&lt;br /&gt;
                resources: {}&lt;br /&gt;
            };&lt;br /&gt;
            resources = resourcesOf(manifest);&lt;br /&gt;
            Object.keys(resources).forEach(function (title) {&lt;br /&gt;
                var src = resources[title];&lt;br /&gt;
                var dst;&lt;br /&gt;
                if (!src || typeof src !== &#039;object&#039;) return;&lt;br /&gt;
                dst = {};&lt;br /&gt;
                [&#039;revision&#039;, &#039;sha1&#039;, &#039;hash&#039;, &#039;timestamp&#039;, &#039;updatedAt&#039;, &#039;url&#039;].forEach(function (key) {&lt;br /&gt;
                    if (src[key] !== undefined &amp;amp;&amp;amp; src[key] !== null &amp;amp;&amp;amp; String(src[key]) !== &#039;&#039;) dst[key] = src[key];&lt;br /&gt;
                });&lt;br /&gt;
                out.resources[title] = dst;&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function buildLookup(manifest) {&lt;br /&gt;
            var lookup = {};&lt;br /&gt;
            Object.keys(resourcesOf(manifest)).forEach(function (title) {&lt;br /&gt;
                lookup[normalizeManifestTitle(title).toLowerCase()] = resourcesOf(manifest)[title];&lt;br /&gt;
            });&lt;br /&gt;
            return lookup;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function computeChanged(prev, next) {&lt;br /&gt;
            var prevLookup = buildLookup(prev);&lt;br /&gt;
            var nextLookup = buildLookup(next);&lt;br /&gt;
            var out = [];&lt;br /&gt;
            Object.keys(nextLookup).forEach(function (key) {&lt;br /&gt;
                if (resourceToken(prevLookup[key]) !== resourceToken(nextLookup[key])) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(prevLookup).forEach(function (key) {&lt;br /&gt;
                if (!nextLookup[key]) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            return unique(out);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function load(options) {&lt;br /&gt;
            if (loadPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return loadPromise;&lt;br /&gt;
            loadPromise = fetchApi({&lt;br /&gt;
                action: REVISION_MANIFEST_ACTION,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;&lt;br /&gt;
            }).then(function (payload) {&lt;br /&gt;
                var manifest = unwrap(payload);&lt;br /&gt;
                if (!manifest || !manifest.resources) throw new Error(&#039;invalid revision manifest&#039;);&lt;br /&gt;
                current = compactRevisionManifest(manifest) || manifest;&lt;br /&gt;
                changedResources = computeChanged(previous, current);&lt;br /&gt;
                if (changedResources.length &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.invalidateResources === &#039;function&#039;) {&lt;br /&gt;
                    window.EntryCache.invalidateResources(changedResources);&lt;br /&gt;
                }&lt;br /&gt;
                writeLocalJson(REVISION_MANIFEST_LOCAL_KEY, current);&lt;br /&gt;
                previous = current;&lt;br /&gt;
                maybePrune();&lt;br /&gt;
                return current;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                current = previous || null;&lt;br /&gt;
                return current;&lt;br /&gt;
            });&lt;br /&gt;
            return loadPromise;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensureLoaded() {&lt;br /&gt;
            return current ? Promise.resolve(current) : load();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            var lookup;&lt;br /&gt;
            if (!title || !current) return null;&lt;br /&gt;
            lookup = buildLookup(current);&lt;br /&gt;
            return lookup[title.toLowerCase()] || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenForRef(ref) {&lt;br /&gt;
            return resourceToken(resourceForRef(ref));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceKeyForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            return title ? title.toLowerCase() : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForRef(ref, type) {&lt;br /&gt;
            var resourceKey = resourceKeyForRef(ref);&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            if (!resourceKey || !token) return &#039;&#039;;&lt;br /&gt;
            return String(type || &#039;raw&#039;) + &#039;:&#039; + resourceKey + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function manifestToken(extra) {&lt;br /&gt;
            var base = current &amp;amp;&amp;amp; (current.manifestVersion || current.version || current.generatedAt || &#039;&#039;) || &#039;&#039;;&lt;br /&gt;
            return String(base || &#039;no-manifest&#039;) + (extra ? (&#039;:&#039; + String(extra)) : &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenFromUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;);&lt;br /&gt;
            var match = text.match(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=([^&amp;amp;]+)/);&lt;br /&gt;
            if (!match) return &#039;&#039;;&lt;br /&gt;
            try { return decodeURIComponent(match[1]); } catch (err) { return match[1]; }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForUrl(url, type) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var token = tokenFromUrl(text) || manifestToken(&#039;url&#039;);&lt;br /&gt;
            var normalized;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                normalized = new URL(text, window.location.href);&lt;br /&gt;
                text = normalized.pathname + (normalized.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            text = text.replace(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=[^&amp;amp;]*/g, &#039;&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            try { text = decodeURI(text); } catch (err2) {}&lt;br /&gt;
            return String(type || &#039;blob&#039;) + &#039;:url:&#039; + text.toLowerCase() + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function addRevisionParam(url, ref) {&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var sep;&lt;br /&gt;
            if (!text || !token || /[?&amp;amp;]_entryRev=/.test(text)) return text;&lt;br /&gt;
            sep = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + sep + &#039;_entryRev=&#039; + encodeURIComponent(token);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function maybePrune() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var due;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            due = !last || (now() - last &amp;gt;= days * 24 * 60 * 60 * 1000);&lt;br /&gt;
            if (due &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.prune === &#039;function&#039;) {&lt;br /&gt;
                window.EntryCache.prune(days);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function countdown() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var next;&lt;br /&gt;
            var remain;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            if (!last) return { days: days, lastClientPruneAt: null, nextClientPruneAt: null, remainingMs: 0, remainingDays: 0, due: true };&lt;br /&gt;
            next = last + days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            remain = Math.max(0, next - now());&lt;br /&gt;
            return {&lt;br /&gt;
                days: days,&lt;br /&gt;
                lastClientPruneAt: last,&lt;br /&gt;
                nextClientPruneAt: next,&lt;br /&gt;
                remainingMs: remain,&lt;br /&gt;
                remainingDays: Math.ceil(remain / (24 * 60 * 60 * 1000)),&lt;br /&gt;
                due: remain &amp;lt;= 0&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function status() {&lt;br /&gt;
            return {&lt;br /&gt;
                available: !!current,&lt;br /&gt;
                manifestVersion: current &amp;amp;&amp;amp; (current.manifestVersion || current.version || &#039;&#039;),&lt;br /&gt;
                generatedAt: current &amp;amp;&amp;amp; current.generatedAt || &#039;&#039;,&lt;br /&gt;
                changedResources: changedResources.slice(0, 50),&lt;br /&gt;
                changedCount: changedResources.length,&lt;br /&gt;
                prune: countdown(),&lt;br /&gt;
                cache: window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.info === &#039;function&#039; ? window.EntryCache.info() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            load: load,&lt;br /&gt;
            ensureLoaded: ensureLoaded,&lt;br /&gt;
            current: function () { return current; },&lt;br /&gt;
            resourceForRef: resourceForRef,&lt;br /&gt;
            tokenForRef: tokenForRef,&lt;br /&gt;
            cacheKeyForRef: cacheKeyForRef,&lt;br /&gt;
            cacheKeyForUrl: cacheKeyForUrl,&lt;br /&gt;
            manifestToken: manifestToken,&lt;br /&gt;
            resourceKeyForRef: resourceKeyForRef,&lt;br /&gt;
            addRevisionParam: addRevisionParam,&lt;br /&gt;
            maybePrune: maybePrune,&lt;br /&gt;
            countdown: countdown,&lt;br /&gt;
            status: status&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.RevisionManifest = window.RevisionManifest || createRevisionManifestService();&lt;br /&gt;
&lt;br /&gt;
    function createEntryStore() {&lt;br /&gt;
        var jsonCache = {};&lt;br /&gt;
        var flagUrlCache = {};&lt;br /&gt;
        var fileUrlCache = {};&lt;br /&gt;
        var imageReadyCache = {};&lt;br /&gt;
        var imageObjectCache = {};&lt;br /&gt;
        var imageDisplayUrlCache = {};&lt;br /&gt;
        var imagePromiseCache = {};&lt;br /&gt;
        var rawPromiseCache = {};&lt;br /&gt;
        var textCache = {};&lt;br /&gt;
        var textPromiseCache = {};&lt;br /&gt;
&lt;br /&gt;
        function fetchJsonRef(ref, options) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var promiseKey;&lt;br /&gt;
            var url;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty json ref&#039;));&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(ref, &#039;json&#039;) : &#039;&#039;;&lt;br /&gt;
            promiseKey = cacheKey ? (key + &#039;@&#039; + cacheKey) : key;&lt;br /&gt;
            if (jsonCache[promiseKey]) return Promise.resolve(jsonCache[promiseKey].data);&lt;br /&gt;
            if (rawPromiseCache[promiseKey]) return rawPromiseCache[promiseKey];&lt;br /&gt;
            url = rawUrlForRef(ref, &#039;application/json&#039;);&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
            }&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(ref) : key;&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(ref) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                rawPromiseCache[promiseKey] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        var cachedData = cachedText &amp;amp;&amp;amp; cachedText.trim() ? JSON.parse(cachedText) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: cachedData, loadedAt: now(), persistent: true };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        return cachedData;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token });&lt;br /&gt;
                        return data;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return rawPromiseCache[promiseKey];&lt;br /&gt;
            }&lt;br /&gt;
            rawPromiseCache[promiseKey] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                return data;&lt;br /&gt;
            });&lt;br /&gt;
            return rawPromiseCache[promiseKey];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getJsonSync(ref) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            return key &amp;amp;&amp;amp; jsonCache[key] ? jsonCache[key].data : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setJsonRef(ref, data) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            jsonCache[key] = { key: key, ref: ref, url: rawUrlForRef(ref, &#039;application/json&#039;), data: data, loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeFile(value) {&lt;br /&gt;
            return String(value || &#039;&#039;)&lt;br /&gt;
                .replace(/^(?:file|파일):/i, &#039;&#039;)&lt;br /&gt;
                .trim();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileKey(value) {&lt;br /&gt;
            return normalizeFile(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setFlagUrl(file, url) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            flagUrlCache[key] = String(url || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getFlagUrl(file) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            return key ? (flagUrlCache[key] || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFlagUrls(files) {&lt;br /&gt;
            var clean = unique((files || []).map(normalizeFile).filter(Boolean));&lt;br /&gt;
&lt;br /&gt;
            function cacheKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;flagurl&#039;) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function resourceKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + fileKey(file));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function tokenForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function hydrateCachedFlagUrl(file) {&lt;br /&gt;
                var key = fileKey(file);&lt;br /&gt;
                var cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                if (!key || flagUrlCache[key] !== undefined || !cacheKey || !window.EntryCache || typeof window.EntryCache.getText !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.resolve(false);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        flagUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return true;&lt;br /&gt;
                    }&lt;br /&gt;
                    return false;&lt;br /&gt;
                }).catch(function () { return false; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(clean.map(hydrateCachedFlagUrl)).then(function () {&lt;br /&gt;
                var pending = clean.filter(function (file) {&lt;br /&gt;
                    return getFlagUrl(file) === &#039;&#039; &amp;amp;&amp;amp; flagUrlCache[fileKey(file)] === undefined;&lt;br /&gt;
                });&lt;br /&gt;
                var chunks = [];&lt;br /&gt;
&lt;br /&gt;
                while (pending.length) chunks.push(pending.splice(0, 20));&lt;br /&gt;
                if (!chunks.length) return flagUrlCache;&lt;br /&gt;
&lt;br /&gt;
                return Promise.all(chunks.map(function (chunk) {&lt;br /&gt;
                    var titleToKey = {};&lt;br /&gt;
                    var keyToFile = {};&lt;br /&gt;
                    var titles = [];&lt;br /&gt;
                    chunk.forEach(function (file) {&lt;br /&gt;
                        var key = fileKey(file);&lt;br /&gt;
                        if (!key) return;&lt;br /&gt;
                        titleToKey[fileKey(&#039;File:&#039; + file)] = key;&lt;br /&gt;
                        titleToKey[fileKey(&#039;파일:&#039; + file)] = key;&lt;br /&gt;
                        keyToFile[key] = file;&lt;br /&gt;
                        titles.push(&#039;File:&#039; + file);&lt;br /&gt;
                        titles.push(&#039;파일:&#039; + file);&lt;br /&gt;
                    });&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size&#039;,&lt;br /&gt;
                        iiurlwidth: &#039;16&#039;,&lt;br /&gt;
                        titles: titles.join(&#039;|&#039;)&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var seen = {};&lt;br /&gt;
                        pages.forEach(function (page) {&lt;br /&gt;
                            var key = titleToKey[fileKey(page &amp;amp;&amp;amp; page.title)] || fileKey(page &amp;amp;&amp;amp; page.title);&lt;br /&gt;
                            var file = keyToFile[key] || (page &amp;amp;&amp;amp; page.title || &#039;&#039;).replace(/^(?:File|파일):/i, &#039;&#039;);&lt;br /&gt;
                            var imageinfo = page &amp;amp;&amp;amp; page.imageinfo &amp;amp;&amp;amp; page.imageinfo[0];&lt;br /&gt;
                            var url;&lt;br /&gt;
                            var rev;&lt;br /&gt;
                            var sep;&lt;br /&gt;
                            var cacheKey;&lt;br /&gt;
                            if (!key) return;&lt;br /&gt;
                            seen[key] = true;&lt;br /&gt;
                            url = imageinfo &amp;amp;&amp;amp; (imageinfo.thumburl || imageinfo.url) ? (imageinfo.thumburl || imageinfo.url) : &#039;&#039;;&lt;br /&gt;
                            if (url) {&lt;br /&gt;
                                rev = imageinfo &amp;amp;&amp;amp; (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) ? (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) : &#039;&#039;;&lt;br /&gt;
                                if (rev &amp;amp;&amp;amp; !/[?&amp;amp;]_entryFileRev=/.test(url)) {&lt;br /&gt;
                                    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                                    url += sep + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                                }&lt;br /&gt;
                            }&lt;br /&gt;
                            flagUrlCache[key] = url;&lt;br /&gt;
                            cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.putText === &#039;function&#039;) {&lt;br /&gt;
                                window.EntryCache.putText(cacheKey, url || &#039;&#039;, {&lt;br /&gt;
                                    resourceKey: resourceKeyForFile(file),&lt;br /&gt;
                                    token: tokenForFile(file),&lt;br /&gt;
                                    kind: &#039;flagurl&#039;,&lt;br /&gt;
                                    contentType: &#039;text/plain; charset=UTF-8&#039;&lt;br /&gt;
                                });&lt;br /&gt;
                            }&lt;br /&gt;
                        });&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; !seen[key] &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () { return flagUrlCache; });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeGenericFileTitle(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            var match;&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            for (i = 0; i &amp;lt; 4; i += 1) {&lt;br /&gt;
                try {&lt;br /&gt;
                    if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;).replace(/^\/+/, &#039;&#039;).replace(/^index\.php\/?/i, &#039;&#039;).replace(/^wiki\/?/i, &#039;&#039;).trim();&lt;br /&gt;
            match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/(.+)$/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^(?:File|파일|Image|이미지)\s*:/i, &#039;&#039;).replace(/^:+/, &#039;&#039;).trim();&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isFileRef(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            return /^(?:file|파일)\s*:/i.test(text) || /(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\//i.test(text);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileUrlKey(value) {&lt;br /&gt;
            return normalizeGenericFileTitle(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function stableDirectImageUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var separator;&lt;br /&gt;
            var bust;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            if (/[?&amp;amp;]_entryAsset=/.test(text) || /[?&amp;amp;]_=/.test(text)) return text;&lt;br /&gt;
            bust = String(BUILD_ID || &#039;entry&#039;);&lt;br /&gt;
            separator = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + separator + &#039;_entryAsset=&#039; + encodeURIComponent(bust);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFileUrl(ref) {&lt;br /&gt;
            var original = String(ref || &#039;&#039;).trim();&lt;br /&gt;
            var file;&lt;br /&gt;
            var key;&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!original) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (!isFileRef(original)) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            file = normalizeGenericFileTitle(original);&lt;br /&gt;
            key = fileUrlKey(file);&lt;br /&gt;
            if (!file || !key) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            if (fileUrlCache[key]) return Promise.resolve(fileUrlCache[key]);&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;fileurl&#039;) : &#039;&#039;;&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + key);&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        fileUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return fileUrlCache[key];&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                        titles: &#039;File:&#039; + file&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                        var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                        var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                        var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                        var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                        fileUrlCache[key] = resolved;&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, resolved, { resourceKey: resourceKey, token: token, kind: &#039;fileurl&#039;, contentType: &#039;text/plain; charset=UTF-8&#039; });&lt;br /&gt;
                        return resolved;&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return stableDirectImageUrl(original);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
            return fetchApi({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;,&lt;br /&gt;
                redirects: &#039;1&#039;,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + file&lt;br /&gt;
            }).then(function (json) {&lt;br /&gt;
                var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                fileUrlCache[key] = resolved;&lt;br /&gt;
                return resolved;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                return stableDirectImageUrl(original);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImageUrl(url, options) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var sourcePromise;&lt;br /&gt;
            var persistent = !(options &amp;amp;&amp;amp; options.persistent === false);&lt;br /&gt;
            if (!key) return Promise.resolve(false);&lt;br /&gt;
            if (imageReadyCache[key]) return Promise.resolve(true);&lt;br /&gt;
            if (imagePromiseCache[key]) return imagePromiseCache[key];&lt;br /&gt;
&lt;br /&gt;
            cacheKey = persistent &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039; ? window.RevisionManifest.cacheKeyForUrl(key, &#039;image&#039;) : &#039;&#039;;&lt;br /&gt;
            sourcePromise = Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.fetchBlobUrl === &#039;function&#039;) {&lt;br /&gt;
                sourcePromise = window.EntryCache.fetchBlobUrl(key, cacheKey, {&lt;br /&gt;
                    resourceKey: &#039;&#039;,&lt;br /&gt;
                    token: &#039;&#039;,&lt;br /&gt;
                    kind: &#039;image&#039;&lt;br /&gt;
                }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            imagePromiseCache[key] = sourcePromise.then(function (cachedObjectUrl) {&lt;br /&gt;
                return new Promise(function (resolve) {&lt;br /&gt;
                    var img = new Image();&lt;br /&gt;
                    var settled = false;&lt;br /&gt;
                    var src = cachedObjectUrl || key;&lt;br /&gt;
                    function finish(ok) {&lt;br /&gt;
                        if (settled) return;&lt;br /&gt;
                        settled = true;&lt;br /&gt;
                        if (ok) {&lt;br /&gt;
                            imageReadyCache[key] = true;&lt;br /&gt;
                            imageReadyCache[src] = true;&lt;br /&gt;
                            imageObjectCache[key] = img;&lt;br /&gt;
                            imageObjectCache[src] = img;&lt;br /&gt;
                            imageDisplayUrlCache[key] = src;&lt;br /&gt;
                            imageDisplayUrlCache[src] = src;&lt;br /&gt;
                        }&lt;br /&gt;
                        resolve(!!ok);&lt;br /&gt;
                    }&lt;br /&gt;
                    img.onload = function () {&lt;br /&gt;
                        if (img.decode) {&lt;br /&gt;
                            img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        } else {&lt;br /&gt;
                            finish(true);&lt;br /&gt;
                        }&lt;br /&gt;
                    };&lt;br /&gt;
                    img.onerror = function () {&lt;br /&gt;
                        if (src !== key) {&lt;br /&gt;
                            src = key;&lt;br /&gt;
                            img.src = key;&lt;br /&gt;
                            return;&lt;br /&gt;
                        }&lt;br /&gt;
                        finish(false);&lt;br /&gt;
                    };&lt;br /&gt;
                    img.decoding = &#039;async&#039;;&lt;br /&gt;
                    img.loading = &#039;eager&#039;;&lt;br /&gt;
                    img.src = src;&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        if (img.decode) img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        else finish(true);&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
            return imagePromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImages(urls, options) {&lt;br /&gt;
            var list = unique((urls || []).filter(Boolean));&lt;br /&gt;
            var limit = options &amp;amp;&amp;amp; Number(options.limit);&lt;br /&gt;
            var concurrency = Math.max(1, Math.min(48, Number(options &amp;amp;&amp;amp; options.concurrency) || 24));&lt;br /&gt;
            var index = 0;&lt;br /&gt;
            var ok = 0;&lt;br /&gt;
            var fail = 0;&lt;br /&gt;
            if (Number.isFinite(limit) &amp;amp;&amp;amp; limit &amp;gt; 0) list = list.slice(0, limit);&lt;br /&gt;
            if (!list.length) return Promise.resolve({ total: 0, ok: 0, fail: 0 });&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                function pump() {&lt;br /&gt;
                    while (index &amp;lt; list.length &amp;amp;&amp;amp; concurrency &amp;gt; 0) {&lt;br /&gt;
                        (function (url) {&lt;br /&gt;
                            concurrency -= 1;&lt;br /&gt;
                            preloadImageUrl(url, options || {}).then(function (result) {&lt;br /&gt;
                                if (result) ok += 1;&lt;br /&gt;
                                else fail += 1;&lt;br /&gt;
                            }).catch(function () {&lt;br /&gt;
                                fail += 1;&lt;br /&gt;
                            }).then(function () {&lt;br /&gt;
                                concurrency += 1;&lt;br /&gt;
                                if (index &amp;gt;= list.length &amp;amp;&amp;amp; ok + fail &amp;gt;= list.length) resolve({ total: list.length, ok: ok, fail: fail });&lt;br /&gt;
                                else pump();&lt;br /&gt;
                            });&lt;br /&gt;
                        })(list[index++]);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                pump();&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isImageReady(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return !!(key &amp;amp;&amp;amp; imageReadyCache[key]);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageElement(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageObjectCache[key] || null) : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageDisplayUrl(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageDisplayUrlCache[key] || key) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeUrlKey(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var a;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                a = document.createElement(&#039;a&#039;);&lt;br /&gt;
                a.href = text;&lt;br /&gt;
                text = a.pathname + (a.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            try {&lt;br /&gt;
                text = decodeURI(text);&lt;br /&gt;
            } catch (err2) {}&lt;br /&gt;
            text = text.replace(/([?&amp;amp;])_=[^&amp;amp;]*/g, &#039;$1&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            text = text.replace(/_/g, &#039;_&#039;);&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchTextUrl(url, options) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceRef;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty text url&#039;));&lt;br /&gt;
            if (textCache[key]) return Promise.resolve(textCache[key].text);&lt;br /&gt;
            if (textPromiseCache[key]) return textPromiseCache[key];&lt;br /&gt;
            resourceRef = options &amp;amp;&amp;amp; options.resourceRef ? options.resourceRef : (options &amp;amp;&amp;amp; options.ref ? options.ref : &#039;&#039;);&lt;br /&gt;
            cacheKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(resourceRef, &#039;text&#039;) : &#039;&#039;;&lt;br /&gt;
            if (!cacheKey &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039;) cacheKey = window.RevisionManifest.cacheKeyForUrl(url, &#039;text&#039;);&lt;br /&gt;
            resourceKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(resourceRef) : (&#039;url:&#039; + key);&lt;br /&gt;
            token = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(resourceRef) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                textPromiseCache[key] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: cachedText, loadedAt: now(), persistent: true };&lt;br /&gt;
                        return cachedText;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token, kind: &#039;text&#039;, contentType: &#039;text/html; charset=UTF-8&#039; });&lt;br /&gt;
                        return text;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return textPromiseCache[key];&lt;br /&gt;
            }&lt;br /&gt;
            textPromiseCache[key] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                return text;&lt;br /&gt;
            });&lt;br /&gt;
            return textPromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getTextSync(url) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            return key &amp;amp;&amp;amp; textCache[key] ? textCache[key].text : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setTextUrl(url, text) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            textCache[key] = { key: key, url: url, text: String(text || &#039;&#039;), loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheInfo() {&lt;br /&gt;
            return {&lt;br /&gt;
                json: Object.keys(jsonCache).length,&lt;br /&gt;
                jsonKeys: Object.keys(jsonCache),&lt;br /&gt;
                flags: Object.keys(flagUrlCache).length,&lt;br /&gt;
                files: Object.keys(fileUrlCache).length,&lt;br /&gt;
                images: Object.keys(imageReadyCache).length,&lt;br /&gt;
                retainedImages: Object.keys(imageObjectCache).length,&lt;br /&gt;
                displayImages: Object.keys(imageDisplayUrlCache).length,&lt;br /&gt;
                text: Object.keys(textCache).length,&lt;br /&gt;
                textKeys: Object.keys(textCache)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            fetchJsonRef: fetchJsonRef,&lt;br /&gt;
            getJsonSync: getJsonSync,&lt;br /&gt;
            setJsonRef: setJsonRef,&lt;br /&gt;
            normalizeRefKey: normalizeRefKey,&lt;br /&gt;
            rawUrlForRef: rawUrlForRef,&lt;br /&gt;
            revisionUrlForRef: function (ref, ctype) {&lt;br /&gt;
                var url = rawUrlForRef(ref, ctype);&lt;br /&gt;
                if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                    url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
                }&lt;br /&gt;
                return url;&lt;br /&gt;
            },&lt;br /&gt;
            resolveFlagUrls: resolveFlagUrls,&lt;br /&gt;
            setFlagUrl: setFlagUrl,&lt;br /&gt;
            getFlagUrl: getFlagUrl,&lt;br /&gt;
            resolveFileUrl: resolveFileUrl,&lt;br /&gt;
            preloadImageUrl: preloadImageUrl,&lt;br /&gt;
            preloadImages: preloadImages,&lt;br /&gt;
            isImageReady: isImageReady,&lt;br /&gt;
            getImageElement: getImageElement,&lt;br /&gt;
            getImageDisplayUrl: getImageDisplayUrl,&lt;br /&gt;
            fetchTextUrl: fetchTextUrl,&lt;br /&gt;
            getTextSync: getTextSync,&lt;br /&gt;
            setTextUrl: setTextUrl,&lt;br /&gt;
            stableDirectImageUrl: stableDirectImageUrl,&lt;br /&gt;
            cacheInfo: cacheInfo&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryStore = window.EntryStore || createEntryStore();&lt;br /&gt;
&lt;br /&gt;
    function updateBootProgress(done, total, label) {&lt;br /&gt;
        var pct = total ? Math.max(0, Math.min(100, Math.round((done / total) * 100))) : 0;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootDetailNode &amp;amp;&amp;amp; label) bootDetailNode.textContent = label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function adoptBootScreen(node) {&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        bootNode = node;&lt;br /&gt;
        bootStatusNode = bootNode.querySelector(&#039;.boot-gate-status&#039;);&lt;br /&gt;
        bootProgressNode = bootNode.querySelector(&#039;.boot-gate-progress&#039;);&lt;br /&gt;
        bootDetailNode = bootNode.querySelector(&#039;.boot-gate-detail&#039;);&lt;br /&gt;
        bootFillNode = bootNode.querySelector(&#039;.boot-gate-meter-fill&#039;);&lt;br /&gt;
        bootNode.classList.add(&#039;is-active&#039;);&lt;br /&gt;
        bootNode.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBootScreen(options) {&lt;br /&gt;
        var panel;&lt;br /&gt;
        var header;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var existing;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
        var previewMode;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        previewMode = !!options.preview;&lt;br /&gt;
        if (bootNode &amp;amp;&amp;amp; bootNode.parentNode) return bootNode;&lt;br /&gt;
        if (!document.body) return null;&lt;br /&gt;
&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !previewMode) return null;&lt;br /&gt;
        if (!previewMode) activateBootSurface();&lt;br /&gt;
&lt;br /&gt;
        existing = document.getElementById(&#039;boot-gate-screen&#039;) || (!previewMode &amp;amp;&amp;amp; window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.ensure ? window.__BootGatePrelude.ensure() : null);&lt;br /&gt;
        if (existing) return adoptBootScreen(existing);&lt;br /&gt;
&lt;br /&gt;
        bootNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootNode.id = &#039;boot-gate-screen&#039;;&lt;br /&gt;
        bootNode.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        bootNode.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        bootNode.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        header = document.createElement(&#039;div&#039;);&lt;br /&gt;
        header.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        header.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootStatusNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootStatusNode.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        bootStatusNode.textContent = &#039;Preparing entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        bootFillNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootFillNode.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(bootFillNode);&lt;br /&gt;
&lt;br /&gt;
        bootProgressNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootProgressNode.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        bootProgressNode.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootDetailNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootDetailNode.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        bootDetailNode.textContent = &#039;loading manifest&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { hideBootScreen({ force: true }); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(header);&lt;br /&gt;
        panel.appendChild(bootStatusNode);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(bootProgressNode);&lt;br /&gt;
        panel.appendChild(bootDetailNode);&lt;br /&gt;
        bootNode.appendChild(decoLayer);&lt;br /&gt;
        bootNode.appendChild(panel);&lt;br /&gt;
        bootNode.appendChild(close);&lt;br /&gt;
        document.body.appendChild(bootNode);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildLoginUrl() {&lt;br /&gt;
        var pageName = String(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        var params = {};&lt;br /&gt;
&lt;br /&gt;
        if (pageName &amp;amp;&amp;amp; !isAuthenticationPage() &amp;amp;&amp;amp; !isCreateAccountPage()) params.returnto = pageName;&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                return mw.util.getUrl(&#039;Special:UserLogin&#039;, params);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return &#039;/index.php?title=Special%3AUserLogin&#039; + (params.returnto ? &#039;&amp;amp;returnto=&#039; + encodeURIComponent(params.returnto) : &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureLoginGateAction(panel) {&lt;br /&gt;
        var copy;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (loginGateActionNode &amp;amp;&amp;amp; loginGateActionNode.parentNode) return loginGateActionNode;&lt;br /&gt;
        loginGateActionNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        loginGateActionNode.className = &#039;boot-gate-login&#039;;&lt;br /&gt;
&lt;br /&gt;
        copy = document.createElement(&#039;div&#039;);&lt;br /&gt;
        copy.className = &#039;boot-gate-login-copy&#039;;&lt;br /&gt;
        copy.textContent = &#039;SIGN IN TO ENTER THE WIKI.&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = document.createElement(&#039;a&#039;);&lt;br /&gt;
        action.className = &#039;boot-gate-login-action&#039;;&lt;br /&gt;
        action.href = buildLoginUrl();&lt;br /&gt;
        action.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        action.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
&lt;br /&gt;
        loginGateActionNode.appendChild(copy);&lt;br /&gt;
        loginGateActionNode.appendChild(action);&lt;br /&gt;
        panel.appendChild(loginGateActionNode);&lt;br /&gt;
        return loginGateActionNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showLoginGate() {&lt;br /&gt;
        var node = ensureBootScreen();&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (!node || !requiresLoginGate()) return false;&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        loginGateLocked = true;&lt;br /&gt;
        node.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.add(&#039;is-active&#039;, &#039;is-login-required&#039;);&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;dialog&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-modal&#039;, &#039;true&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;off&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = node.querySelector(&#039;.boot-gate-panel&#039;);&lt;br /&gt;
        title = node.querySelector(&#039;.boot-gate-title&#039;);&lt;br /&gt;
        if (title) title.textContent = &#039;ACCOUNT AUTHENTICATION&#039;;&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = &#039;LOGIN REQUIRED&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = &#039;100%&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = panel ? ensureLoginGateAction(panel).querySelector(&#039;.boot-gate-login-action&#039;) : null;&lt;br /&gt;
        if (action) {&lt;br /&gt;
            action.href = buildLoginUrl();&lt;br /&gt;
            window.requestAnimationFrame(function () {&lt;br /&gt;
                try { action.focus({ preventScroll: true }); }&lt;br /&gt;
                catch (err) { try { action.focus(); } catch (ignore) {} }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hideBootScreen(options) {&lt;br /&gt;
        var node = bootNode || document.getElementById(&#039;boot-gate-screen&#039;);&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        if (loginGateLocked &amp;amp;&amp;amp; requiresLoginGate() &amp;amp;&amp;amp; !options.force) return false;&lt;br /&gt;
        if (!node) {&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            if (bootNode === node) bootNode = null;&lt;br /&gt;
            loginGateActionNode = null;&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 240);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromNationPayload(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            if (typeof value === &#039;string&#039;) {&lt;br /&gt;
                files.push(value);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof value === &#039;object&#039;) files.push(value.file || value.flag_file || value.flag || value.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function scanItem(item) {&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            if (Array.isArray(item.flags)) item.flags.forEach(add);&lt;br /&gt;
            add(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        (payload &amp;amp;&amp;amp; payload.continents || []).forEach(function (continent) {&lt;br /&gt;
            (continent.regions || []).forEach(function (region) {&lt;br /&gt;
                (region.items || []).forEach(scanItem);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromLinkMap(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        var source = payload &amp;amp;&amp;amp; payload.items ? payload.items : {};&lt;br /&gt;
        Object.keys(source || {}).forEach(function (key) {&lt;br /&gt;
            var item = source[key];&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            files.push(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prewarmImages(urls, limit) {&lt;br /&gt;
        return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
            limit: Number(limit) &amp;gt; 0 ? Number(limit) : 0,&lt;br /&gt;
            concurrency: 16,&lt;br /&gt;
            persistent: false&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationRuntime() {&lt;br /&gt;
        return window.Decorations || window.CLBI_DECORATIONS || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationRuntime() {&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var tries = 0;&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var runtime = getDecorationRuntime();&lt;br /&gt;
                if (runtime) return resolve(runtime);&lt;br /&gt;
                tries += 1;&lt;br /&gt;
                if (tries &amp;gt; 40) return resolve(null);&lt;br /&gt;
                window.setTimeout(tick, 25);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesDecorationEntry(entry, filter) {&lt;br /&gt;
        var era = String(filter &amp;amp;&amp;amp; filter.era || &#039;&#039;).trim();&lt;br /&gt;
        var page = String(filter &amp;amp;&amp;amp; filter.page || &#039;&#039;).trim();&lt;br /&gt;
        var entryPage = String(entry &amp;amp;&amp;amp; entry.page || &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039;) return false;&lt;br /&gt;
        if (page &amp;amp;&amp;amp; entryPage &amp;amp;&amp;amp; entryPage !== page) return false;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() !== era) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDecorationSet(task, level) {&lt;br /&gt;
        var ref = task.ref || &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
        return window.EntryStore.fetchJsonRef(ref, { noStore: !!task.noStore }).then(function (registry) {&lt;br /&gt;
            var list = registry &amp;amp;&amp;amp; Array.isArray(registry.decorations) ? registry.decorations : [];&lt;br /&gt;
            var pixelRefs = [];&lt;br /&gt;
            list.forEach(function (entry) {&lt;br /&gt;
                var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).toLowerCase();&lt;br /&gt;
                var asset = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
                if (!asset) return;&lt;br /&gt;
                if (type !== &#039;pixel-json&#039; &amp;amp;&amp;amp; !/\.json(?:[?#].*)?$/i.test(asset)) return;&lt;br /&gt;
                if (!matchesDecorationEntry(entry, task)) return;&lt;br /&gt;
                pixelRefs.push(asset);&lt;br /&gt;
            });&lt;br /&gt;
            if (level !== &#039;full&#039; || task.preparePixels === false || !pixelRefs.length) return registry;&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (!runtime || typeof runtime.preparePixelCanvas !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                        return window.EntryStore.fetchJsonRef(pixelRef).catch(function () { return null; });&lt;br /&gt;
                    })).then(function () { return registry; });&lt;br /&gt;
                }&lt;br /&gt;
                return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(pixelRef).catch(function () { return null; });&lt;br /&gt;
                })).then(function () { return registry; });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareNationsEra(task, level) {&lt;br /&gt;
        var era = String(task.era || &#039;1950&#039;);&lt;br /&gt;
        var listRef = task.listRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_List.json&#039;);&lt;br /&gt;
        var linkRef = task.linkMapRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_Link_Map.json&#039;);&lt;br /&gt;
        var jsonPhaseId = BootPerf.start(&#039;nations-era json fetch&#039;, { era: era, level: level, listRef: listRef, linkRef: linkRef });&lt;br /&gt;
        var listPromise = window.EntryStore.fetchJsonRef(listRef).catch(function () { return null; });&lt;br /&gt;
        var linkPromise = window.EntryStore.fetchJsonRef(linkRef).catch(function () { return null; });&lt;br /&gt;
&lt;br /&gt;
        return Promise.all([listPromise, linkPromise]).then(function (results) {&lt;br /&gt;
            BootPerf.end(jsonPhaseId, { ok: true });&lt;br /&gt;
            var files;&lt;br /&gt;
            var flagUrls;&lt;br /&gt;
            if (level !== &#039;full&#039; &amp;amp;&amp;amp; level !== &#039;warm&#039;) return results;&lt;br /&gt;
            files = unique(collectFlagsFromNationPayload(results[0]).concat(collectFlagsFromLinkMap(results[1])));&lt;br /&gt;
            return BootPerf.measure(&#039;nations-era flag url resolve&#039;, { era: era, count: files.length }, function () {&lt;br /&gt;
                return window.EntryStore.resolveFlagUrls(files);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                if (level === &#039;warm&#039;) {&lt;br /&gt;
                    /*&lt;br /&gt;
                     * Warm tab contract — 20260708.&lt;br /&gt;
                     *&lt;br /&gt;
                     * A previous tab already stored the same-revision flag URL and image&lt;br /&gt;
                     * blobs.  Re-decoding every flag here makes the second tab feel as slow&lt;br /&gt;
                     * as the first one.  In warm mode we only hydrate the flag URL table from&lt;br /&gt;
                     * EntryCache so NationsPanel can render stable URLs immediately; image&lt;br /&gt;
                     * decode remains demand-driven by the live panel instead of blocking the&lt;br /&gt;
                     * boot screen.&lt;br /&gt;
                     */&lt;br /&gt;
                    return results;&lt;br /&gt;
                }&lt;br /&gt;
                flagUrls = files.map(function (file) { return window.EntryStore.getFlagUrl(file); }).filter(Boolean);&lt;br /&gt;
                /*&lt;br /&gt;
                 * 20260710: Flag URLs are part of the entry contract, but decoding every&lt;br /&gt;
                 * flag image is not.  The 1950 list has more than 150 flags, and waiting&lt;br /&gt;
                 * for all image decodes kept the public boot gate open for several seconds.&lt;br /&gt;
                 * Keep revision-aware URLs ready for the panel, then let the browser load&lt;br /&gt;
                 * the actual images from the live DOM.  A low-priority background warm-up&lt;br /&gt;
                 * may run after the boot gate has opened, so it never competes with the&lt;br /&gt;
                 * current entry surface or the globe ready contract.&lt;br /&gt;
                 */&lt;br /&gt;
                BootPerf.mark(&#039;nations-era flag image prewarm skipped&#039;, {&lt;br /&gt;
                    era: era,&lt;br /&gt;
                    count: flagUrls.length,&lt;br /&gt;
                    limit: task.flagLimit == null ? 0 : Number(task.flagLimit),&lt;br /&gt;
                    reason: &#039;deferred-after-boot-gate&#039;&lt;br /&gt;
                });&lt;br /&gt;
                if (flagUrls.length &amp;amp;&amp;amp; task.deferFlagImages !== false) {&lt;br /&gt;
                    deferredEntryWarmups.push(function () {&lt;br /&gt;
                        return BootPerf.measure(&#039;deferred nations-era flag image prewarm&#039;, {&lt;br /&gt;
                            era: era,&lt;br /&gt;
                            count: flagUrls.length,&lt;br /&gt;
                            limit: task.flagLimit == null ? 0 : Number(task.flagLimit)&lt;br /&gt;
                        }, function () {&lt;br /&gt;
                            return prewarmImages(flagUrls, task.flagLimit == null ? 0 : Number(task.flagLimit));&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                return results;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareGlobeSharedAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;globe-shared url resolve&#039;, { count: refs.length, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            /*&lt;br /&gt;
             * Globe shared assets are heavy 6K texture sources.  The live&lt;br /&gt;
             * NationsGlobe ready contract already waits for the actual Three.js&lt;br /&gt;
             * texture path, so decoding the same images here only duplicates work&lt;br /&gt;
             * and holds the boot screen.  Keep revision-aware URL resolution in the&lt;br /&gt;
             * entry ledger, but make the image warm-up non-blocking.&lt;br /&gt;
             */&lt;br /&gt;
            BootPerf.mark(&#039;globe-shared image prewarm skipped&#039;, {&lt;br /&gt;
                count: urls.length,&lt;br /&gt;
                level: level,&lt;br /&gt;
                reason: &#039;deferred-to-nations-globe-ready-contract&#039;&lt;br /&gt;
            });&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (!window.EntryStore || typeof window.EntryStore.preloadImages !== &#039;function&#039;) return;&lt;br /&gt;
                window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 4)),&lt;br /&gt;
                    persistent: false&lt;br /&gt;
                }).catch(function () {});&lt;br /&gt;
            }, 0);&lt;br /&gt;
            return urls;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareImageAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        var full = level === &#039;full&#039;;&lt;br /&gt;
        var exposeAs = String(task &amp;amp;&amp;amp; task.exposeAs || &#039;&#039;).trim();&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;image-assets url resolve&#039;, { count: refs.length, exposeAs: exposeAs, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            if (exposeAs === &#039;nationsGlobeLoadingGif&#039;) {&lt;br /&gt;
                window.NationsGlobeLoadingGifRef = refs[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifUrl = urls[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifFile = &#039;Gfx-vhs-glitch-001.gif&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (!full) return urls;&lt;br /&gt;
            return BootPerf.measure(&#039;image-assets image prewarm&#039;, { count: urls.length, exposeAs: exposeAs, limit: task.imageLimit == null ? 0 : Number(task.imageLimit), concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2)) }, function () {&lt;br /&gt;
                return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2))&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () { return urls; });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareHtmlEntry(task) {&lt;br /&gt;
        var url = String(task &amp;amp;&amp;amp; (task.url || task.ref) || &#039;&#039;).trim();&lt;br /&gt;
        if (!url) return Promise.resolve(null);&lt;br /&gt;
        return window.EntryStore.fetchTextUrl(url, { noStore: !!task.noStore, resourceRef: task.resourceRef || task.ref || task.title || &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareTask(task, defaultLevel) {&lt;br /&gt;
        var level = String(task &amp;amp;&amp;amp; (task.level || defaultLevel) || &#039;half&#039;).toLowerCase();&lt;br /&gt;
        var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
        if (!task || typeof task !== &#039;object&#039;) return Promise.resolve(null);&lt;br /&gt;
        if (type === &#039;html&#039;) return prepareHtmlEntry(task);&lt;br /&gt;
        if (type === &#039;json&#039;) return window.EntryStore.fetchJsonRef(task.ref, { noStore: !!task.noStore });&lt;br /&gt;
        if (type === &#039;pixel-json&#039;) {&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (level === &#039;full&#039; &amp;amp;&amp;amp; runtime &amp;amp;&amp;amp; typeof runtime.preparePixelCanvas === &#039;function&#039;) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(task.ref || task.asset);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(task.ref || task.asset);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        if (type === &#039;decorations&#039;) return prepareDecorationSet(task, level);&lt;br /&gt;
        if (type === &#039;nations-era&#039;) return prepareNationsEra(task, level);&lt;br /&gt;
        if (type === &#039;globe-shared-assets&#039;) return prepareGlobeSharedAssets(task, level);&lt;br /&gt;
        if (type === &#039;image-assets&#039;) return prepareImageAssets(task, level);&lt;br /&gt;
        return Promise.resolve(null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifest() {&lt;br /&gt;
        return BootPerf.measure(&#039;revision manifest load&#039;, {}, function () {&lt;br /&gt;
            return (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.load === &#039;function&#039; ? window.RevisionManifest.load() : Promise.resolve(null));&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;entry manifest json fetch&#039;, { ref: MANIFEST_TITLE }, function () {&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(MANIFEST_TITLE, { noStore: false });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (manifest) {&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039; || !manifest.version) {&lt;br /&gt;
                BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: &#039;invalid manifest&#039; });&lt;br /&gt;
                return defaultManifest;&lt;br /&gt;
            }&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest ready&#039;, { version: manifest.version });&lt;br /&gt;
            return manifest;&lt;br /&gt;
        }).catch(function (err) {&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: err &amp;amp;&amp;amp; (err.message || String(err)) || &#039;load failed&#039; });&lt;br /&gt;
            return defaultManifest;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function flattenInitialTasks(manifest) {&lt;br /&gt;
        var initial = manifest &amp;amp;&amp;amp; manifest.initial ? manifest.initial : {};&lt;br /&gt;
        var full = Array.isArray(initial.full) ? initial.full : [];&lt;br /&gt;
        var half = Array.isArray(initial.half) ? initial.half : [];&lt;br /&gt;
        var tasks = [];&lt;br /&gt;
        full.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;full&#039;;&lt;br /&gt;
            task.blocking = true;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        half.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;half&#039;;&lt;br /&gt;
            task.blocking = false;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        return tasks;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function flattenWarmInitialTasks(tasks) {&lt;br /&gt;
        var warmed = [];&lt;br /&gt;
        (tasks || []).forEach(function (task) {&lt;br /&gt;
            var copy;&lt;br /&gt;
            var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
            if (!task || task.blocking === false) return;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Warm boot fast path — 20260708.&lt;br /&gt;
             *&lt;br /&gt;
             * Cold boot intentionally performs the expensive work: resolving every flag,&lt;br /&gt;
             * downloading/decoding images, preparing decoration canvases, and warming shared&lt;br /&gt;
             * globe textures.  Once a tab has certified the pack against the current revision&lt;br /&gt;
             * manifest, later tabs must not replay that full workload.  They only hydrate the&lt;br /&gt;
             * small tables needed by the live page and let already-cached image/blob data be&lt;br /&gt;
             * consumed on demand.  This is the missing layer that made second tabs feel almost&lt;br /&gt;
             * as slow as first tabs even though the string checks were true.&lt;br /&gt;
             */&lt;br /&gt;
            if (type === &#039;globe-shared-assets&#039;) return;&lt;br /&gt;
            if (type === &#039;image-assets&#039;) return;&lt;br /&gt;
            copy = Object.assign({}, task);&lt;br /&gt;
            if (type === &#039;nations-era&#039;) copy.level = &#039;warm&#039;;&lt;br /&gt;
            else if (type === &#039;decorations&#039;) {&lt;br /&gt;
                copy.level = &#039;half&#039;;&lt;br /&gt;
                copy.preparePixels = false;&lt;br /&gt;
            } else if (type === &#039;pixel-json&#039;) copy.level = &#039;half&#039;;&lt;br /&gt;
            warmed.push(copy);&lt;br /&gt;
        });&lt;br /&gt;
        return warmed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackKey(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        return &#039;initial-entry-full:&#039; + version;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackToken(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        var revToken = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(version) : version;&lt;br /&gt;
        return revToken;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isInitialPackWarm(manifest) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        return !!(window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.packReady === &#039;function&#039; &amp;amp;&amp;amp; window.EntryCache.packReady(key, token));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function markInitialPackWarm(manifest, meta) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        if (window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.setPackReady === &#039;function&#039;) {&lt;br /&gt;
            window.EntryCache.setPackReady(key, token, Object.assign({ manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || BUILD_ID }, meta || {}));&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function waitMs(ms) {&lt;br /&gt;
        return new Promise(function (resolve) { window.setTimeout(resolve, Math.max(0, ms || 0)); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForBody() {&lt;br /&gt;
        if (document.body) return Promise.resolve(document.body);&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                if (document.body) return resolve(document.body);&lt;br /&gt;
                window.setTimeout(tick, 10);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDomReady() {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) return Promise.resolve();&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, resolve, { once: true });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForAnimationFrames(count) {&lt;br /&gt;
        count = Math.max(1, Math.round(count || 1));&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function next(left) {&lt;br /&gt;
                if (left &amp;lt;= 0) return resolve();&lt;br /&gt;
                window.requestAnimationFrame(function () { next(left - 1); });&lt;br /&gt;
            }&lt;br /&gt;
            next(count);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitUntil(predicate, options) {&lt;br /&gt;
        var started = now();&lt;br /&gt;
        var timeout = options &amp;amp;&amp;amp; options.timeoutMs ? options.timeoutMs : 8000;&lt;br /&gt;
        var interval = options &amp;amp;&amp;amp; options.intervalMs ? options.intervalMs : 60;&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var result = false;&lt;br /&gt;
                try { result = predicate(); } catch (err) { result = false; }&lt;br /&gt;
                if (result) return resolve({ ok: true, value: result });&lt;br /&gt;
                if (now() - started &amp;gt;= timeout) return resolve({ ok: false, timeout: true });&lt;br /&gt;
                window.setTimeout(tick, interval);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForTrackedScripts() {&lt;br /&gt;
        var list = (window.EntryScriptLoads || []).slice();&lt;br /&gt;
        if (!list.length) return Promise.resolve([]);&lt;br /&gt;
        return Promise.all(list.map(function (promise) {&lt;br /&gt;
            return Promise.resolve(promise).catch(function (err) { return { ok: false, error: err }; });&lt;br /&gt;
        }));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDocumentSurface() {&lt;br /&gt;
        return waitForDomReady().then(function () {&lt;br /&gt;
            return waitForBody();&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                return document.querySelector(&#039;.content-wrapper&#039;) &amp;amp;&amp;amp; document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
            }, { timeoutMs: 8000, intervalMs: 50 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isNationsPageSurface() {&lt;br /&gt;
        var page = String(mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
        return !!document.querySelector(&#039;.clbi-nations-panel-stack&#039;) || /(?:^|[_ ])시대(?:$|[_ ])/.test(page) || /(?:^|[_ ])Era(?:$|[_ ])/i.test(page);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getInitialNationsEra(manifest) {&lt;br /&gt;
        var tasks = flattenInitialTasks(manifest);&lt;br /&gt;
        var i;&lt;br /&gt;
        for (i = 0; i &amp;lt; tasks.length; i += 1) {&lt;br /&gt;
            if (tasks[i] &amp;amp;&amp;amp; tasks[i].type === &#039;nations-era&#039; &amp;amp;&amp;amp; String(tasks[i].level || &#039;&#039;).toLowerCase() === &#039;full&#039;) {&lt;br /&gt;
                return String(tasks[i].era || &#039;1950&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;1950&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsPanelReady(era) {&lt;br /&gt;
        return BootPerf.measure(&#039;wait nations panel api&#039;, { era: era }, function () {&lt;br /&gt;
            return waitUntil(function () { return window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.whenEraReady === &#039;function&#039;; }, {&lt;br /&gt;
                timeoutMs: 8000,&lt;br /&gt;
                intervalMs: 50&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (result) {&lt;br /&gt;
            if (!result.ok || !window.NationsPanel || typeof window.NationsPanel.whenEraReady !== &#039;function&#039;) return null;&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel era ready&#039;, { era: era }, function () {&lt;br /&gt;
                return window.NationsPanel.whenEraReady(era, { timeoutMs: 15000 }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel dom ready&#039;, { era: era }, function () {&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var panel = document.querySelector(&#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + era + &#039;&amp;quot;] .clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;) ||&lt;br /&gt;
                        document.querySelector(&#039;.clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
                    return panel &amp;amp;&amp;amp; panel.classList.contains(&#039;clbi-nations-list-json-ready&#039;) &amp;amp;&amp;amp; panel.getAttribute(&#039;data-nation-list-year-loaded&#039;) === String(era);&lt;br /&gt;
                }, { timeoutMs: 15000, intervalMs: 80 });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsGlobeReady(options) {&lt;br /&gt;
        var globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
        var soft;&lt;br /&gt;
        var timeout;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        soft = !!options.soft;&lt;br /&gt;
        timeout = Number(options.timeoutMs || (soft ? 450 : 30000));&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            BootPerf.mark(&#039;wait nations globe skipped&#039;, { reason: &#039;no globe node&#039;, soft: soft });&lt;br /&gt;
            return Promise.resolve(null);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return BootPerf.measure(soft ? &#039;wait nations globe warm attach&#039; : &#039;wait nations globe ready contract&#039;, { soft: soft, timeoutMs: timeout }, function () {&lt;br /&gt;
            if (soft) {&lt;br /&gt;
                /*&lt;br /&gt;
                Warm-tab policy:&lt;br /&gt;
                Raw globe images are already revision-checked and blob-cached by EntryCache,&lt;br /&gt;
                but WebGL scene creation, GPU upload, topojson parsing, and per-tab Three.js&lt;br /&gt;
                objects cannot be shared across browser tabs.  Waiting for the full is-ready&lt;br /&gt;
                contract here makes a warm tab slower than the first tab.  In warm mode the&lt;br /&gt;
                initial gate only waits until the globe consumer has been attached, then the&lt;br /&gt;
                globe finishes its own per-tab hydrate without holding the whole page hostage.&lt;br /&gt;
                */&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                    if (!node) return false;&lt;br /&gt;
                    return node.getAttribute(&#039;data-nations-globe-ready&#039;) === &#039;1&#039; ||&lt;br /&gt;
                        !!node.CLBI_NationsGlobeInstance ||&lt;br /&gt;
                        !!node.querySelector(&#039;.clbi-nations-globe-stage&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;is-ready&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;has-error&#039;);&lt;br /&gt;
                }, { timeoutMs: timeout, intervalMs: 40 });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                return node &amp;amp;&amp;amp; (node.classList.contains(&#039;is-ready&#039;) || node.classList.contains(&#039;has-error&#039;));&lt;br /&gt;
            }, { timeoutMs: timeout, intervalMs: 100 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationsReady(options) {&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
            if (!runtime) return null;&lt;br /&gt;
            if (options.warm) {&lt;br /&gt;
                /*&lt;br /&gt;
                 * Warm tab contract — 20260708.&lt;br /&gt;
                 *&lt;br /&gt;
                 * The decoration registry and pixel canvases were prepared by the cold tab.&lt;br /&gt;
                 * Calling reload() here re-reads and re-syncs the decoration layer during the&lt;br /&gt;
                 * boot gate, which is visible as a pointless delay.  A warm tab only needs the&lt;br /&gt;
                 * current visibility pass so boot can release quickly and editing/preview tools&lt;br /&gt;
                 * remain responsive.&lt;br /&gt;
                 */&lt;br /&gt;
                try {&lt;br /&gt;
                    if (typeof runtime.updateVisibility === &#039;function&#039;) runtime.updateVisibility(document);&lt;br /&gt;
                    else if (typeof runtime.sync === &#039;function&#039;) runtime.sync();&lt;br /&gt;
                } catch (err) {}&lt;br /&gt;
                return waitForAnimationFrames(1);&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof runtime.reload !== &#039;function&#039;) return null;&lt;br /&gt;
            return runtime.reload().catch(function () { return null; }).then(function () {&lt;br /&gt;
                return waitForAnimationFrames(2);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForCurrentEntrySurface(manifest, options) {&lt;br /&gt;
        var era = getInitialNationsEra(manifest);&lt;br /&gt;
        var warmPack;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        warmPack = !!options.warmPack;&lt;br /&gt;
&lt;br /&gt;
        if (!isNationsPageSurface()) {&lt;br /&gt;
            return waitForDecorationsReady({ warm: !!warmPack }).then(function () { return waitForAnimationFrames(1); });&lt;br /&gt;
        }&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = warmPack ? &#039;Hydrating current information surface from warm cache&#039; : &#039;Preparing current information surface&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for era &#039; + era + &#039; ready contract&#039;;&lt;br /&gt;
        return waitForNationsPanelReady(era)&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (warmPack) {&lt;br /&gt;
                    if (bootDetailNode) bootDetailNode.textContent = &#039;attaching globe warm consumer&#039;;&lt;br /&gt;
                    return waitForNationsGlobeReady({ soft: true, timeoutMs: 450 });&lt;br /&gt;
                }&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for globe ready contract&#039;;&lt;br /&gt;
                return waitForNationsGlobeReady();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for decoration surface&#039;;&lt;br /&gt;
                return waitForDecorationsReady({ warm: warmPack });&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () { return waitForAnimationFrames(warmPack ? 1 : 2); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runDeferredEntryWarmups() {&lt;br /&gt;
        var queue = deferredEntryWarmups.splice(0);&lt;br /&gt;
        if (!queue.length) return;&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            var chain = Promise.resolve();&lt;br /&gt;
            queue.forEach(function (job) {&lt;br /&gt;
                chain = chain.then(function () {&lt;br /&gt;
                    try { return job(); }&lt;br /&gt;
                    catch (err) { return null; }&lt;br /&gt;
                }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }, 1200);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runInitialLoad(options) {&lt;br /&gt;
        var done = 0;&lt;br /&gt;
        var manifestRef;&lt;br /&gt;
        var tasks;&lt;br /&gt;
        var bootOptions;&lt;br /&gt;
        var timedOut = false;&lt;br /&gt;
        var warmPack = false;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        bootStartTime = window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.startTime ? window.__BootGatePrelude.startTime : now();&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        ensureBootScreen();&lt;br /&gt;
        updateBootProgress(0, 1, &#039;loading entry manifest&#039;);&lt;br /&gt;
&lt;br /&gt;
        manifestRef = loadManifest().then(function (manifest) {&lt;br /&gt;
            var minDisplay;&lt;br /&gt;
            var totalSteps;&lt;br /&gt;
            var maxBlockingMs;&lt;br /&gt;
            var timeoutHandle;&lt;br /&gt;
            bootOptions = manifest.boot || {};&lt;br /&gt;
            warmPack = isInitialPackWarm(manifest);&lt;br /&gt;
            minDisplay = options.minDisplayMs || (warmPack ? (bootOptions.cachedMinDisplayMs || 220) : (bootOptions.minDisplayMs || 950));&lt;br /&gt;
            tasks = flattenInitialTasks(manifest);&lt;br /&gt;
            if (!tasks.length) tasks = flattenInitialTasks(defaultManifest);&lt;br /&gt;
            if (warmPack) tasks = flattenWarmInitialTasks(tasks);&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            Full/half contract:&lt;br /&gt;
            These manifest tasks only prepare data assets.  The gate is not allowed to open&lt;br /&gt;
            until the current page surface has consumed that data and reported a real ready&lt;br /&gt;
            state below.  Do not reintroduce localStorage-only skip logic here; a cached&lt;br /&gt;
            version number is not the same as current-tab readiness.&lt;br /&gt;
            */&lt;br /&gt;
            totalSteps = tasks.length + 4;&lt;br /&gt;
            updateBootProgress(0, totalSteps, warmPack ? &#039;hydrating warm entry cache&#039; : &#039;starting entry packs&#039;);&lt;br /&gt;
&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                maxBlockingMs = Number(options.maxBlockingMs || bootOptions.maxBlockingMs || 30000);&lt;br /&gt;
                timeoutHandle = window.setTimeout(function () {&lt;br /&gt;
                    timedOut = true;&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }, maxBlockingMs);&lt;br /&gt;
&lt;br /&gt;
                Promise.all(tasks.map(function (task) {&lt;br /&gt;
                    var label = task.label || task.id || task.type || &#039;entry task&#039;;&lt;br /&gt;
                    return BootPerf.measure(&#039;boot task: &#039; + label, { id: task.id || &#039;&#039;, type: task.type || &#039;&#039;, level: task.level || task.level === 0 ? task.level : &#039;&#039; }, function () {&lt;br /&gt;
                        return prepareTask(task, task.level);&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }).then(function () {&lt;br /&gt;
                        done += 1;&lt;br /&gt;
                        updateBootProgress(done, totalSteps, label);&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;loading subsystem scripts&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait tracked subsystem scripts&#039;, { count: (window.EntryScriptLoads || []).length }, function () { return waitForTrackedScripts(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for document shell&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait document shell&#039;, {}, function () { return waitForDocumentSurface(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for current entry surface&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait current entry surface&#039;, { warmPack: warmPack }, function () { return waitForCurrentEntrySurface(manifest, { warmPack: warmPack }); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(totalSteps, totalSteps, warmPack ? &#039;warm entry surface complete&#039; : &#039;entry surface complete&#039;);&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                var elapsed = now() - bootStartTime;&lt;br /&gt;
                var wait = Math.max(0, minDisplay - elapsed);&lt;br /&gt;
                return waitMs(wait);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                localStorage.setItem(READY_KEY, String(manifest.version));&lt;br /&gt;
                if (!timedOut) markInitialPackWarm(manifest, { warmSource: warmPack ? &#039;cache-hit&#039; : &#039;cold-build&#039; });&lt;br /&gt;
                if (bootStatusNode) bootStatusNode.textContent = timedOut ? &#039;Entry surface ready with deferred items&#039; : (warmPack ? &#039;Entry surface ready from warm cache&#039; : &#039;Entry surface ready&#039;);&lt;br /&gt;
                updateBootProgress(tasks.length + 4, tasks.length + 4, timedOut ? &#039;timeout: continuing deferred loading&#039; : (warmPack ? &#039;warm cache complete&#039; : &#039;complete&#039;));&lt;br /&gt;
                BootPerf.mark(&#039;boot gate complete&#039;, { timedOut: timedOut, warmPack: warmPack, manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || &#039;&#039; });&lt;br /&gt;
                BootPerf.print();&lt;br /&gt;
                runDeferredEntryWarmups();&lt;br /&gt;
                window.setTimeout(function () {&lt;br /&gt;
                    if (requiresLoginGate()) showLoginGate();&lt;br /&gt;
                    else hideBootScreen();&lt;br /&gt;
                }, 120);&lt;br /&gt;
                return manifest;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return manifestRef;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startBoot(options) {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return Promise.resolve({ skipped: true, reason: &#039;developer-or-editing-page&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        if (hasBootParam(&#039;0&#039;) &amp;amp;&amp;amp; !requiresLoginGate() &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return Promise.resolve(null);&lt;br /&gt;
        if (bootStarted &amp;amp;&amp;amp; bootPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return bootPromise;&lt;br /&gt;
        bootStarted = true;&lt;br /&gt;
        bootPromise = runInitialLoad(options || {});&lt;br /&gt;
        return bootPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resetBoot() {&lt;br /&gt;
        localStorage.removeItem(READY_KEY);&lt;br /&gt;
        bootStarted = false;&lt;br /&gt;
        bootPromise = null;&lt;br /&gt;
        loginGateLocked = false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    BootGate intentionally has no SPA hold/release API.&lt;br /&gt;
    Initial boot is the only blocking phase; later SPA routes must consume prepared&lt;br /&gt;
    EntryStore artifacts without reopening the loading surface.&lt;br /&gt;
    */&lt;br /&gt;
&lt;br /&gt;
    function buildBootReport() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD_ID,&lt;br /&gt;
            state: {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                excludedPage: BOOT_EXCLUDED_PAGE,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;))&lt;br /&gt;
            },&lt;br /&gt;
            store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
            bootPerf: window.BootPerf &amp;amp;&amp;amp; typeof window.BootPerf.summary === &#039;function&#039; ? window.BootPerf.summary() : null,&lt;br /&gt;
            revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null,&lt;br /&gt;
            nations: window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.diagnostics === &#039;function&#039; ? window.NationsPanel.diagnostics() : null,&lt;br /&gt;
            decorations: window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.diagnostics === &#039;function&#039; ? window.Decorations.diagnostics() : null&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryLoader = window.EntryLoader || {&lt;br /&gt;
        loadManifest: loadManifest,&lt;br /&gt;
        prepareTask: prepareTask,&lt;br /&gt;
        prepareNationsEra: prepareNationsEra,&lt;br /&gt;
        prepareDecorationSet: prepareDecorationSet,&lt;br /&gt;
        prepareGlobeSharedAssets: prepareGlobeSharedAssets,&lt;br /&gt;
        prepareImageAssets: prepareImageAssets,&lt;br /&gt;
        prepareHtmlEntry: prepareHtmlEntry,&lt;br /&gt;
        runInitialLoad: runInitialLoad&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function showBootPreview(options) {&lt;br /&gt;
        var node;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        /*&lt;br /&gt;
         * Boot preview is a design/editing surface, not a real boot gate.&lt;br /&gt;
         * The earlier preview reused activateBootSurface(), which applied&lt;br /&gt;
         * html.boot-gate-active and hid the entire wiki shell, including&lt;br /&gt;
         * DevTools.  That made it impossible to edit loading-screen&lt;br /&gt;
         * decorations while previewing them.  Keep the real first-entry gate&lt;br /&gt;
         * full-screen, but make preview a small non-blocking surface below the&lt;br /&gt;
         * DevTools z-index so the owner can keep using the editor.&lt;br /&gt;
         */&lt;br /&gt;
        node = ensureBootScreen({ preview: true });&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        node.classList.add(&#039;is-preview&#039;);&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = options.status || &#039;Loading screen preview&#039;;&lt;br /&gt;
        updateBootProgress(Number(options.progress || 64), 100, options.detail || &#039;preview mode: no entry tasks are running&#039;);&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BootGate = window.BootGate || {&lt;br /&gt;
        version: BUILD_ID,&lt;br /&gt;
        start: startBoot,&lt;br /&gt;
        show: function (options) {&lt;br /&gt;
            options = options || {};&lt;br /&gt;
            options.force = true;&lt;br /&gt;
            resetBoot();&lt;br /&gt;
            return startBoot(options);&lt;br /&gt;
        },&lt;br /&gt;
        reset: resetBoot,&lt;br /&gt;
        hide: hideBootScreen,&lt;br /&gt;
        preview: showBootPreview,&lt;br /&gt;
        report: buildBootReport,&lt;br /&gt;
        state: function () {&lt;br /&gt;
            return {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                prelude: !!window.__BootGatePrelude,&lt;br /&gt;
                store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
                revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function prime() {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        waitForBody().then(function () {&lt;br /&gt;
            ensureBootScreen();&lt;br /&gt;
            return waitForAnimationFrames(1);&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            startBoot();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    waitForBody().then(function () {&lt;br /&gt;
        if (!BOOT_EXCLUDED_PAGE) ensureBootScreen();&lt;br /&gt;
        else hideBootScreen();&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(prime, 0);&lt;br /&gt;
})(window, document, window.mediaWiki || window.mw);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:DevTools.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsPanel.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsGlobe.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* CLBI safety guard: adaptive reset functions must exist before shell metric callbacks run. */&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.resetLeftRecentAdaptiveState = resetLeftRecentAdaptiveState;&lt;br /&gt;
window.resetLeftBillboardAdaptiveState = resetLeftBillboardAdaptiveState;&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:AnecdoteViewer.js&#039;);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SYSTEM_TITLE_NAMESPACES = {&lt;br /&gt;
        &#039;-1&#039;: true,&lt;br /&gt;
        &#039;4&#039;: true,&lt;br /&gt;
        &#039;5&#039;: true,&lt;br /&gt;
        &#039;6&#039;: true,&lt;br /&gt;
        &#039;7&#039;: true,&lt;br /&gt;
        &#039;8&#039;: true,&lt;br /&gt;
        &#039;9&#039;: true,&lt;br /&gt;
        &#039;10&#039;: true,&lt;br /&gt;
        &#039;11&#039;: true,&lt;br /&gt;
        &#039;12&#039;: true,&lt;br /&gt;
        &#039;13&#039;: true,&lt;br /&gt;
        &#039;14&#039;: true,&lt;br /&gt;
        &#039;15&#039;: true,&lt;br /&gt;
        &#039;828&#039;: true,&lt;br /&gt;
        &#039;829&#039;: true&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function normalizePageNameForShell(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readCurrentPageNameForShell() {&lt;br /&gt;
        var pageName = mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (pageName) {&lt;br /&gt;
            return normalizePageNameForShell(pageName);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return normalizePageNameForShell(window.location.pathname || &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnecdoteNamespaceForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var canonicalNamespace = String(mw.config.get(&#039;wgCanonicalNamespace&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 3000 ||&lt;br /&gt;
            canonicalNamespace === &#039;anecdote&#039; ||&lt;br /&gt;
            /^(anecdote|에넥도트):/i.test(pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBackendOrSystemPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var action = String(mw.config.get(&#039;wgAction&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var lowerPageName = pageName.toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === &#039;대문&#039;) {&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (SYSTEM_TITLE_NAMESPACES[String(namespaceNumber)]) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;json&#039; || contentModel === &#039;sanitized-css&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/\.(css|js|json)$/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/^(mediawiki|미디어위키|special|특수):/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMediaWikiSystemAssetPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 8 &amp;amp;&amp;amp;&lt;br /&gt;
            (/\.(css|js)$/i.test(pageName) || contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;sanitized-css&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var systemDocRawFetchToken = 0;&lt;br /&gt;
&lt;br /&gt;
    function cleanupLegacySystemDocCodeMutationsForShell() {&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codepane&#039;).forEach(function (pane) {&lt;br /&gt;
            var parent;&lt;br /&gt;
&lt;br /&gt;
            if (!pane || !pane.parentNode) return;&lt;br /&gt;
&lt;br /&gt;
            parent = pane.parentNode;&lt;br /&gt;
            while (pane.firstChild) {&lt;br /&gt;
                parent.insertBefore(pane.firstChild, pane);&lt;br /&gt;
            }&lt;br /&gt;
            parent.removeChild(pane);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codebox&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;style&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocOutputForShell() {&lt;br /&gt;
        return document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findSystemDocSourceNodeForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var children;&lt;br /&gt;
        var preferred;&lt;br /&gt;
&lt;br /&gt;
        if (!output) return null;&lt;br /&gt;
&lt;br /&gt;
        children = Array.prototype.slice.call(output.children || [])&lt;br /&gt;
            .filter(function (el) {&lt;br /&gt;
                return el &amp;amp;&amp;amp; el.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-doc-indicator-row&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-source-viewer&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    !el.classList.contains(&#039;catlinks&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    (el.textContent || &#039;&#039;).trim().length &amp;gt; 200;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
        preferred = children.filter(function (el) {&lt;br /&gt;
            return el.matches &amp;amp;&amp;amp; el.matches(&#039;.mw-highlight, .mw-code, pre&#039;);&lt;br /&gt;
        })[0];&lt;br /&gt;
&lt;br /&gt;
        return preferred || children.sort(function (a, b) {&lt;br /&gt;
            return (b.textContent || &#039;&#039;).trim().length - (a.textContent || &#039;&#039;).trim().length;&lt;br /&gt;
        })[0] || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocRawUrlForShell() {&lt;br /&gt;
        var title = mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell();&lt;br /&gt;
        var url;&lt;br /&gt;
&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            url = mw.util.getUrl(title, {&lt;br /&gt;
                action: &#039;raw&#039;,&lt;br /&gt;
                ctype: &#039;text/plain&#039;&lt;br /&gt;
            });&lt;br /&gt;
        } else {&lt;br /&gt;
            url = &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/plain&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, title);&lt;br /&gt;
        }&lt;br /&gt;
        return url;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (viewer &amp;amp;&amp;amp; viewer.parentNode) {&lt;br /&gt;
            viewer.parentNode.removeChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-original-source-hidden&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-source-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureSystemDocSourceViewerForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var source;&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var fallbackText;&lt;br /&gt;
&lt;br /&gt;
        if (!output || !isMediaWikiSystemAssetPageForShell()) return null;&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
&lt;br /&gt;
        source = findSystemDocSourceNodeForShell();&lt;br /&gt;
        if (!source) return null;&lt;br /&gt;
&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!viewer) {&lt;br /&gt;
            viewer = document.createElement(&#039;pre&#039;);&lt;br /&gt;
            viewer.id = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            viewer.className = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            output.appendChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fallbackText = source.textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!viewer.textContent &amp;amp;&amp;amp; fallbackText) {&lt;br /&gt;
            viewer.textContent = fallbackText;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        source.classList.add(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
        source.setAttribute(&#039;data-clbi-system-source-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        source.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
        return viewer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var token;&lt;br /&gt;
        var currentScrollTop;&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = String(mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell());&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        시스템 문서 뷰어가 이미 만들어져 있고 raw 원문도 로드된 상태라면&lt;br /&gt;
        다시 source 탐색/숨김/스타일 재적용을 하지 않는다.&lt;br /&gt;
        DevTools Elements 패널에서 body가 계속 파랗게 깜빡이던 원인은&lt;br /&gt;
        MutationObserver가 이 재적용을 반복해서 DOM attribute mutation을 만들었기 때문이다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            viewer &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-title&#039;) === pageName &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-loaded&#039;) === &#039;1&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        viewer = ensureSystemDocSourceViewerForShell();&lt;br /&gt;
        if (!viewer) return;&lt;br /&gt;
&lt;br /&gt;
        currentScrollTop = viewer.scrollTop || 0;&lt;br /&gt;
        viewer.setAttribute(&#039;data-clbi-raw-title&#039;, pageName);&lt;br /&gt;
        token = ++systemDocRawFetchToken;&lt;br /&gt;
&lt;br /&gt;
        fetch(getSystemDocRawUrlForShell(), { credentials: &#039;same-origin&#039; })&lt;br /&gt;
            .then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;raw fetch failed &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (token !== systemDocRawFetchToken) return;&lt;br /&gt;
&lt;br /&gt;
                currentScrollTop = viewer.scrollTop || currentScrollTop || 0;&lt;br /&gt;
&lt;br /&gt;
                if (text &amp;amp;&amp;amp; viewer.textContent !== text) {&lt;br /&gt;
                    viewer.textContent = text;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;1&#039;);&lt;br /&gt;
                viewer.scrollTop = currentScrollTop;&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;0&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocIndicatorForShell() {&lt;br /&gt;
        var existing = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            document.body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (existing &amp;amp;&amp;amp; existing.parentNode) {&lt;br /&gt;
            existing.parentNode.removeChild(existing);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        removeSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocIndicatorForShell() {&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var extMatch;&lt;br /&gt;
        var ext;&lt;br /&gt;
        var row;&lt;br /&gt;
        var box;&lt;br /&gt;
        var meta;&lt;br /&gt;
        var label;&lt;br /&gt;
        var type;&lt;br /&gt;
        var title;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var main;&lt;br /&gt;
&lt;br /&gt;
        if (!document.body || !isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = readCurrentPageNameForShell();&lt;br /&gt;
        extMatch = pageName.match(/\.(css|js)$/i);&lt;br /&gt;
        ext = extMatch ? extMatch[1].toUpperCase() : &#039;DOC&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.classList.add(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        row = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!row) {&lt;br /&gt;
            row = document.createElement(&#039;div&#039;);&lt;br /&gt;
            row.id = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
            row.className = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
&lt;br /&gt;
            box = document.createElement(&#039;div&#039;);&lt;br /&gt;
            box.className = &#039;clbi-system-doc-indicator&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta = document.createElement(&#039;div&#039;);&lt;br /&gt;
            meta.className = &#039;clbi-system-doc-meta&#039;;&lt;br /&gt;
&lt;br /&gt;
            label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            label.className = &#039;clbi-system-doc-label&#039;;&lt;br /&gt;
            label.textContent = &#039;SYSTEM DOCUMENT&#039;;&lt;br /&gt;
&lt;br /&gt;
            type = document.createElement(&#039;span&#039;);&lt;br /&gt;
            type.className = &#039;clbi-system-doc-type&#039;;&lt;br /&gt;
&lt;br /&gt;
            title = document.createElement(&#039;div&#039;);&lt;br /&gt;
            title.className = &#039;clbi-system-doc-title&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta.appendChild(label);&lt;br /&gt;
            meta.appendChild(type);&lt;br /&gt;
            box.appendChild(meta);&lt;br /&gt;
            box.appendChild(title);&lt;br /&gt;
            row.appendChild(box);&lt;br /&gt;
&lt;br /&gt;
            anchor = getSystemDocOutputForShell();&lt;br /&gt;
            main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (anchor &amp;amp;&amp;amp; anchor.parentNode) {&lt;br /&gt;
                anchor.parentNode.insertBefore(row, anchor);&lt;br /&gt;
            } else if (main) {&lt;br /&gt;
                main.insertBefore(row, main.firstChild);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        type = row.querySelector(&#039;.clbi-system-doc-type&#039;);&lt;br /&gt;
        title = row.querySelector(&#039;.clbi-system-doc-title&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (type) type.textContent = ext;&lt;br /&gt;
        if (title) title.textContent = pageName;&lt;br /&gt;
&lt;br /&gt;
        renderSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var PAGE_TITLE_TARGET_SELECTORS = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var pageShellObserverStarted = false;&lt;br /&gt;
    var pageShellObserverTimer = null;&lt;br /&gt;
&lt;br /&gt;
    function setPageTitleDomHidden(hidden) {&lt;br /&gt;
        var nodes = document.querySelectorAll(PAGE_TITLE_TARGET_SELECTORS.join(&#039;,&#039;));&lt;br /&gt;
&lt;br /&gt;
        nodes.forEach(function (node) {&lt;br /&gt;
            if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
            if (hidden) {&lt;br /&gt;
                node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
                node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
                node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClasses() {&lt;br /&gt;
        var body = document.body;&lt;br /&gt;
        var isSystemPage;&lt;br /&gt;
&lt;br /&gt;
        if (!body) return;&lt;br /&gt;
&lt;br /&gt;
        isSystemPage = isBackendOrSystemPageForShell();&lt;br /&gt;
&lt;br /&gt;
        body.classList.remove(&#039;page-title-hidden&#039;, &#039;page-title-visible&#039;, &#039;backend-system-page&#039;, &#039;anecdote-namespace-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            removeSystemDocIndicatorForShell();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isAnecdoteNamespaceForShell()) {&lt;br /&gt;
            body.classList.add(&#039;anecdote-namespace-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;, &#039;backend-system-page&#039;, &#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
            renderSystemDocIndicatorForShell();&lt;br /&gt;
        } else if (isSystemPage) {&lt;br /&gt;
            body.classList.add(&#039;page-title-visible&#039;, &#039;backend-system-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(false);&lt;br /&gt;
        } else {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClassesDeferred() {&lt;br /&gt;
        applyPageShellClasses();&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 0);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 80);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startPageShellObserver() {&lt;br /&gt;
        var observer;&lt;br /&gt;
&lt;br /&gt;
        if (pageShellObserverStarted || !window.MutationObserver || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
        pageShellObserverStarted = true;&lt;br /&gt;
        observer = new MutationObserver(function (mutations) {&lt;br /&gt;
            var i;&lt;br /&gt;
            var target;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            시스템 CSS/JS 문서는 applyPageShellClasses()가 초기에 한 번&lt;br /&gt;
            인디케이터와 source viewer를 만든 뒤에는 MutationObserver가 다시&lt;br /&gt;
            같은 렌더링을 반복할 필요가 없다. 이 반복이 DevTools에서 body/요소가&lt;br /&gt;
            계속 플래시되는 직접 원인이다.&lt;br /&gt;
            SPA 전환 뒤의 처리는 loadPage()와 wikipage.content hook에서 따로 호출된다.&lt;br /&gt;
            */&lt;br /&gt;
            if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
                for (i = 0; i &amp;lt; mutations.length; i += 1) {&lt;br /&gt;
                    target = mutations[i] &amp;amp;&amp;amp; mutations[i].target;&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        target &amp;amp;&amp;amp;&lt;br /&gt;
                        target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                        (&lt;br /&gt;
                            target.id === &#039;clbi-system-source-viewer&#039; ||&lt;br /&gt;
                            target.id === &#039;clbi-system-doc-indicator-row&#039; ||&lt;br /&gt;
                            (target.closest &amp;amp;&amp;amp; target.closest(&#039;#clbi-system-source-viewer, #clbi-system-doc-indicator-row&#039;))&lt;br /&gt;
                        )&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-doc-indicator-row&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-source-viewer&#039;)&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (pageShellObserverTimer) return;&lt;br /&gt;
&lt;br /&gt;
            pageShellObserverTimer = window.setTimeout(function () {&lt;br /&gt;
                pageShellObserverTimer = null;&lt;br /&gt;
                applyPageShellClasses();&lt;br /&gt;
            }, 50);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        observer.observe(document.body, {&lt;br /&gt;
            childList: true,&lt;br /&gt;
            subtree: true&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {&lt;br /&gt;
            applyPageShellClassesDeferred();&lt;br /&gt;
            startPageShellObserver();&lt;br /&gt;
        });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyPageShellClassesDeferred();&lt;br /&gt;
        startPageShellObserver();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(applyPageShellClassesDeferred);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_PAGE_SHELL = {&lt;br /&gt;
        refresh: applyPageShellClasses,&lt;br /&gt;
        isBackendOrSystemPage: isBackendOrSystemPageForShell,&lt;br /&gt;
        isSystemAssetPage: isMediaWikiSystemAssetPageForShell,&lt;br /&gt;
        renderSystemDocIndicator: renderSystemDocIndicatorForShell,&lt;br /&gt;
        removeSystemDocIndicator: removeSystemDocIndicatorForShell,&lt;br /&gt;
        refreshSystemDocSourceViewer: renderSystemDocSourceViewerForShell&lt;br /&gt;
    };&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
function loadLangScript(done) {&lt;br /&gt;
    $.getScript(&#039;/index.php?title=미디어위키:Lang.js&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;)&lt;br /&gt;
        .done(function() {&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        })&lt;br /&gt;
        .fail(function(a, b, c) {&lt;br /&gt;
            console.error(&#039;Lang.js load failed:&#039;, b, c);&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initHalftoneBackground() {&lt;br /&gt;
    try {&lt;br /&gt;
        initWebGLHalftoneBackground();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;WebGL halftone background failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initWebGLHalftoneBackground() {&lt;br /&gt;
    var canvasId = &#039;site-halftone-bg&#039;;&lt;br /&gt;
    var existing = document.getElementById(canvasId);&lt;br /&gt;
    var canvas = existing || document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    var halftoneState = window.SiteHalftoneBackgroundState || (window.SiteHalftoneBackgroundState = { runId: 0 });&lt;br /&gt;
    var runId = halftoneState.runId + 1;&lt;br /&gt;
&lt;br /&gt;
    halftoneState.runId = runId;&lt;br /&gt;
&lt;br /&gt;
    if (!existing) {&lt;br /&gt;
        canvas.id = canvasId;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        document.body.insertBefore(canvas, document.body.firstChild || null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canvas.style.position = &#039;fixed&#039;;&lt;br /&gt;
    canvas.style.inset = &#039;0&#039;;&lt;br /&gt;
    canvas.style.width = &#039;100vw&#039;;&lt;br /&gt;
    canvas.style.height = &#039;100vh&#039;;&lt;br /&gt;
    canvas.style.pointerEvents = &#039;none&#039;;&lt;br /&gt;
    canvas.style.background = &#039;#000000&#039;;&lt;br /&gt;
    canvas.style.display = &#039;block&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!canvas.getAttribute(&#039;data-halftone-context-watch&#039;)) {&lt;br /&gt;
        canvas.setAttribute(&#039;data-halftone-context-watch&#039;, &#039;1&#039;);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;, function (event) {&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            canvas.style.display = &#039;none&#039;;&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = true;&lt;br /&gt;
                window.SiteHalftoneBackgroundState.runId += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }, false);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextrestored&#039;, function () {&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = false;&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(initWebGLHalftoneBackground, 0);&lt;br /&gt;
        }, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gl = canvas.getContext(&#039;webgl&#039;, {&lt;br /&gt;
        alpha: false,&lt;br /&gt;
        antialias: false,&lt;br /&gt;
        depth: false,&lt;br /&gt;
        stencil: false,&lt;br /&gt;
        preserveDrawingBuffer: false,&lt;br /&gt;
        powerPreference: &#039;low-power&#039;&lt;br /&gt;
    }) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!gl) {&lt;br /&gt;
        canvas.style.display = &#039;none&#039;;&lt;br /&gt;
        console.warn(&#039;WebGL background unavailable.&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexSrc = [&lt;br /&gt;
        &#039;attribute vec2 a_position;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_position, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var fragmentSrc = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_resolution;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;const float TAU = 6.28318530718;&#039;,&lt;br /&gt;
        &#039;float gaussian(float v, float r) {&#039;,&lt;br /&gt;
        &#039;  return exp(-((v * v) / max(0.0001, r * r)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float hash(vec2 p) {&#039;,&lt;br /&gt;
        &#039;  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float bucketAlpha(float a) {&#039;,&lt;br /&gt;
        &#039;  float i = floor(a * 9.0);&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 1.0) return 0.040;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 2.0) return 0.080;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 3.0) return 0.135;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 4.0) return 0.210;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 5.0) return 0.310;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 6.0) return 0.430;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 7.0) return 0.580;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 8.0) return 0.760;&#039;,&lt;br /&gt;
        &#039;  return 0.920;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 frag = gl_FragCoord.xy;&#039;,&lt;br /&gt;
        &#039;  float spacing = 5.0;&#039;,&lt;br /&gt;
        &#039;  float dotSize = 1.08;&#039;,&lt;br /&gt;
        &#039;  vec2 grid = floor(frag / spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 inCell = mod(frag, spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 dotOrigin = vec2(1.0, 1.0);&#039;,&lt;br /&gt;
        &#039;  vec2 dotCenter = dotOrigin + vec2(dotSize * 0.5);&#039;,&lt;br /&gt;
        &#039;  vec2 local = abs(inCell - dotCenter);&#039;,&lt;br /&gt;
        &#039;  float noise = hash(grid);&#039;,&lt;br /&gt;
        &#039;  float size = dotSize + noise * 0.18;&#039;,&lt;br /&gt;
        &#039;  float dotMask = 1.0 - smoothstep(size * 0.5, size * 0.5 + 0.22, max(local.x, local.y));&#039;,&lt;br /&gt;
        &#039;  vec2 uv = frag / u_resolution;&#039;,&lt;br /&gt;
        &#039;  float centerLine = 0.50 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 1.32 + 0.08) * TAU) * 0.070 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 3.18 + 0.34) * TAU) * 0.030;&#039;,&lt;br /&gt;
        &#039;  float u = uv.x - centerLine;&#039;,&lt;br /&gt;
        &#039;  float absU = abs(u);&#039;,&lt;br /&gt;
        &#039;  float sideLift = smoothstep(0.065, 0.44, absU);&#039;,&lt;br /&gt;
        &#039;  float valley = gaussian(u, 0.150);&#039;,&lt;br /&gt;
        &#039;  float t = u_time;&#039;,&lt;br /&gt;
        &#039;  float leftRibbonCenter = -0.28 + sin((uv.y * 3.20 + 0.12) * TAU) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float rightRibbonCenter = 0.27 + sin((uv.y * 2.85 + 0.56) * TAU) * 0.055;&#039;,&lt;br /&gt;
        &#039;  float leftRibbon = gaussian(u - leftRibbonCenter, 0.105);&#039;,&lt;br /&gt;
        &#039;  float rightRibbon = gaussian(u - rightRibbonCenter, 0.110);&#039;,&lt;br /&gt;
        &#039;  float foldedU = u +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 4.40 + 0.22) * TAU) * 0.050 * (0.3 + sideLift) +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 7.20 + uv.x * 1.10) * TAU) * 0.022;&#039;,&lt;br /&gt;
        &#039;  float verticalFold = pow(0.5 + 0.5 * cos(((foldedU * 3.05) + (sin(uv.y * TAU * 2.35) * 0.18)) * TAU), 2.5);&#039;,&lt;br /&gt;
        &#039;  float diagonalFold = pow(0.5 + 0.5 * cos(((foldedU * 1.80) - (uv.y * 1.12) + 0.18) * TAU), 2.1);&#039;,&lt;br /&gt;
        &#039;  float waist = gaussian(uv.y - 0.50, 0.25) * gaussian(absU - 0.20, 0.19);&#039;,&lt;br /&gt;
        &#039;  float grain = (noise - 0.5) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float staticField =&#039;,&lt;br /&gt;
        &#039;    0.055 +&#039;,&lt;br /&gt;
        &#039;    sideLift * 0.210 +&#039;,&lt;br /&gt;
        &#039;    (leftRibbon + rightRibbon) * 0.145 +&#039;,&lt;br /&gt;
        &#039;    verticalFold * (0.055 + sideLift * 0.115) +&#039;,&lt;br /&gt;
        &#039;    diagonalFold * 0.045 +&#039;,&lt;br /&gt;
        &#039;    waist * 0.060 -&#039;,&lt;br /&gt;
        &#039;    valley * 0.150 +&#039;,&lt;br /&gt;
        &#039;    grain;&#039;,&lt;br /&gt;
        &#039;  float alpha = staticField;&#039;,&lt;br /&gt;
        &#039;  alpha += 0.115 * (leftRibbon + rightRibbon) * sin(t * 0.00030 + ((uv.y * 1.9) + sideLift * 0.4) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.095 * verticalFold * (0.4 + sideLift) * sin(t * 0.00041 + ((uv.y * 2.7) + foldedU * 0.65) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.070 * waist * sin(t * 0.00053 + ((uv.y * 3.1) - absU * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.060 * (1.0 - valley) * diagonalFold * sin(t * 0.00067 + ((uv.y * 1.4) + uv.x * 0.6) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.038 * (0.35 + sideLift) * (0.35 + noise) * sin(t * 0.00079 + ((uv.y * 4.6) + noise * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha = bucketAlpha(clamp(alpha, 0.025, 0.96));&#039;,&lt;br /&gt;
        &#039;  float value = alpha * dotMask;&#039;,&lt;br /&gt;
        &#039;  gl_FragColor = vec4(vec3(0.8862745 * value), 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function compileShader(type, source) {&lt;br /&gt;
        var shader = gl.createShader(type);&lt;br /&gt;
        gl.shaderSource(shader, source);&lt;br /&gt;
        gl.compileShader(shader);&lt;br /&gt;
&lt;br /&gt;
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {&lt;br /&gt;
            console.error(&#039;WebGL shader compile error:&#039;, gl.getShaderInfoLog(shader));&lt;br /&gt;
            gl.deleteShader(shader);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return shader;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexShader = compileShader(gl.VERTEX_SHADER, vertexSrc);&lt;br /&gt;
    var fragmentShader = compileShader(gl.FRAGMENT_SHADER, fragmentSrc);&lt;br /&gt;
&lt;br /&gt;
    if (!vertexShader || !fragmentShader) return;&lt;br /&gt;
&lt;br /&gt;
    var program = gl.createProgram();&lt;br /&gt;
    gl.attachShader(program, vertexShader);&lt;br /&gt;
    gl.attachShader(program, fragmentShader);&lt;br /&gt;
    gl.linkProgram(program);&lt;br /&gt;
&lt;br /&gt;
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {&lt;br /&gt;
        console.error(&#039;WebGL program link error:&#039;, gl.getProgramInfoLog(program));&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var positionLoc = gl.getAttribLocation(program, &#039;a_position&#039;);&lt;br /&gt;
    var resolutionLoc = gl.getUniformLocation(program, &#039;u_resolution&#039;);&lt;br /&gt;
    var timeLoc = gl.getUniformLocation(program, &#039;u_time&#039;);&lt;br /&gt;
&lt;br /&gt;
    var buffer = gl.createBuffer();&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([&lt;br /&gt;
        -1, -1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
         1,  1&lt;br /&gt;
    ]), gl.STATIC_DRAW);&lt;br /&gt;
&lt;br /&gt;
    gl.useProgram(program);&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.enableVertexAttribArray(positionLoc);&lt;br /&gt;
    gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
    function resize() {&lt;br /&gt;
        var dpr = Math.min(window.devicePixelRatio || 1, 1.5);&lt;br /&gt;
        var cssW = Math.max(1, window.innerWidth || document.documentElement.clientWidth || 1);&lt;br /&gt;
        var cssH = Math.max(1, window.innerHeight || document.documentElement.clientHeight || 1);&lt;br /&gt;
        var w = Math.max(1, Math.floor(cssW * dpr));&lt;br /&gt;
        var h = Math.max(1, Math.floor(cssH * dpr));&lt;br /&gt;
&lt;br /&gt;
        if (canvas.width !== w || canvas.height !== h) {&lt;br /&gt;
            canvas.width = w;&lt;br /&gt;
            canvas.height = h;&lt;br /&gt;
            canvas.style.width = cssW + &#039;px&#039;;&lt;br /&gt;
            canvas.style.height = cssH + &#039;px&#039;;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var prefersReducedMotion = false;&lt;br /&gt;
    try {&lt;br /&gt;
        prefersReducedMotion = window.matchMedia &amp;amp;&amp;amp; window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches;&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneNode() {&lt;br /&gt;
        return document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneState() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            return &#039;none&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) {&lt;br /&gt;
            return &#039;active&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundBusy === &#039;function&#039; &amp;amp;&amp;amp; instance.isHalftoneBackgroundBusy()) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;present&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function shouldSkipHalftoneDrawForGlobe() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) return false;&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Background pause/resume — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * While the nations globe is being held, keep the last rendered halftone&lt;br /&gt;
         * frame on screen and pause the halftone clock.  The important detail is&lt;br /&gt;
         * that this is not a wall-clock catch-up: skipped seconds are not replayed&lt;br /&gt;
         * or fast-forwarded when the pointer is released.&lt;br /&gt;
         */&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) return true;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; instance.dragging) return true;&lt;br /&gt;
&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundHardBusy === &#039;function&#039;) {&lt;br /&gt;
            return instance.isHalftoneBackgroundHardBusy();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) return true;&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFrameInterval() {&lt;br /&gt;
        if (prefersReducedMotion) {&lt;br /&gt;
            return 1000;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Nations background parity — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * The nations page used to permanently lower the halftone cadence while&lt;br /&gt;
         * the globe existed.  Profiling showed that the steady halftone draw is&lt;br /&gt;
         * normally cheap; the real contention happens during globe drag, first&lt;br /&gt;
         * WebGL texture upload, and long layout/render tasks.  Keep the same&lt;br /&gt;
         * full-quality cadence as ordinary pages, and skip only the frames that&lt;br /&gt;
         * would directly collide with an active/busy globe moment.&lt;br /&gt;
         */&lt;br /&gt;
        return 66;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var lastFrame = 0;&lt;br /&gt;
    var startTime = performance.now();&lt;br /&gt;
    var visualTime = 0;&lt;br /&gt;
    var lastVisualNow = 0;&lt;br /&gt;
&lt;br /&gt;
    function draw(now) {&lt;br /&gt;
        var state = getNationsGlobeHalftoneState();&lt;br /&gt;
        var interval = getFrameInterval();&lt;br /&gt;
        var perf = window.InteractionPerf;&lt;br /&gt;
        var delta;&lt;br /&gt;
&lt;br /&gt;
        if (!lastVisualNow) {&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
        }&lt;br /&gt;
        delta = Math.max(0, Math.min(120, now - lastVisualNow));&lt;br /&gt;
        lastVisualNow = now;&lt;br /&gt;
        visualTime += delta;&lt;br /&gt;
&lt;br /&gt;
        function runDraw() {&lt;br /&gt;
            resize();&lt;br /&gt;
&lt;br /&gt;
            gl.clearColor(0, 0, 0, 1);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.uniform2f(resolutionLoc, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform1f(timeLoc, visualTime);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLES, 0, 6);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (perf &amp;amp;&amp;amp; typeof perf.measureSync === &#039;function&#039;) {&lt;br /&gt;
            return perf.measureSync(&#039;background halftone draw&#039;, { globeState: state, interval: interval }, runDraw);&lt;br /&gt;
        }&lt;br /&gt;
        return runDraw();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(now) {&lt;br /&gt;
        if (halftoneState.runId !== runId || halftoneState.contextLost) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(render);&lt;br /&gt;
&lt;br /&gt;
        if (document.hidden) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (now - lastFrame &amp;lt; getFrameInterval()) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (shouldSkipHalftoneDrawForGlobe()) {&lt;br /&gt;
            lastFrame = now;&lt;br /&gt;
            halftoneState.lastGlobeBusySkipAt = now;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Pause the halftone clock while a hard-busy globe frame owns the&lt;br /&gt;
             * budget.  Without this, the next visible background frame catches&lt;br /&gt;
             * up to wall-clock time and the pattern appears to skip forward.&lt;br /&gt;
             * Updating lastVisualNow here makes the animation resume from the&lt;br /&gt;
             * last visible moment instead of replaying the hidden gap.&lt;br /&gt;
             */&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        draw(now);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    draw(performance.now());&lt;br /&gt;
    requestAnimationFrame(render);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SVG_BELL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_BELL_DOT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell-dot&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M11.68 2.009A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673c-.824-.85-1.678-1.731-2.21-3.348&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;18&amp;quot; cy=&amp;quot;5&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LIST = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-list&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 5h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 12h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 19h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 5h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 12h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 19h13&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LANGUAGES = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-languages&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;m5 8 6 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m4 14 6-6 2-3&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M2 5h12&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 2h1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m22 22-5-10-5 10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 18h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_POWER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-power&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 2v10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.4 6.6a9 9 0 1 1-12.77.04&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SETTINGS = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-settings&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_TEXT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-text&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 8h8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 12h10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 16h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_EYE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-eye&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_NEWSPAPER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-newspaper&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M15 18h-5&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 14h-8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-4 0v-9a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;8&amp;quot; height=&amp;quot;4&amp;quot; x=&amp;quot;10&amp;quot; y=&amp;quot;6&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_GREAT_WALL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-great-wall lucide lucide-paint-roller&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;rect width=&amp;quot;16&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;2&amp;quot; y=&amp;quot;2&amp;quot; rx=&amp;quot;2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M10 16v-2a2 2 0 0 1 2-2h8a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;4&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;8&amp;quot; y=&amp;quot;16&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_TROPHY = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-trophy&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 9h1.5a1 1 0 0 0 0-5H18&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9H4.5a1 1 0 0 1 0-5H6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_PACKAGE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-package&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 3v6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M16.76 3a2 2 0 0 1 1.8 1.1l2.23 4.479a2 2 0 0 1 .21.891V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9.472a2 2 0 0 1 .211-.894L5.45 4.1A2 2 0 0 1 7.24 3z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.054 9.013h17.893&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
var PROFILE_RENDER_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function invalidateProfileRender() {&lt;br /&gt;
    PROFILE_RENDER_TOKEN++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    initHalftoneBackground();&lt;br /&gt;
&lt;br /&gt;
// ── 하단 Plank 단축키 가이드 ──&lt;br /&gt;
function escapeClbiBottomGuideHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiBottomGuidePageName() {&lt;br /&gt;
    var pageName = window.mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) {&lt;br /&gt;
        pageName = window.location.pathname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return String(pageName)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsShortcutContext() {&lt;br /&gt;
    var pageName = readClbiBottomGuidePageName();&lt;br /&gt;
&lt;br /&gt;
    return pageName === &#039;시대&#039; ||&lt;br /&gt;
        pageName === &#039;Era&#039; ||&lt;br /&gt;
        !!document.querySelector(&#039;.clbi-nations-panel-stack, .clbi-nations-globe-window, .clbi-nations-tabpanel&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiWikiEditShortcutContext() {&lt;br /&gt;
    var action = window.mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgAction&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    var search = String(window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
    return action === &#039;edit&#039; ||&lt;br /&gt;
        action === &#039;submit&#039; ||&lt;br /&gt;
        /(?:^|[?&amp;amp;])action=(?:edit|submit)(?:&amp;amp;|$)/.test(search) ||&lt;br /&gt;
        !!document.querySelector(&#039;#editform, #wpSave, input[name=&amp;quot;wpSave&amp;quot;], button[name=&amp;quot;wpSave&amp;quot;]&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function isClbiNationListManagerShortcutContext() {&lt;br /&gt;
    return !!document.querySelector(&#039;.nation-list-manager&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiBottomShortcutItems() {&lt;br /&gt;
    if (isClbiWikiEditShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;변경사항 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationListManagerShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;국가 목록 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Q&#039;, label: &#039;이전 시대&#039; },&lt;br /&gt;
            { key: &#039;E&#039;, label: &#039;다음 시대&#039; },&lt;br /&gt;
            { key: &#039;Shift+Q&#039;, label: &#039;이전 연도&#039; },&lt;br /&gt;
            { key: &#039;Shift+E&#039;, label: &#039;다음 연도&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+Q&#039;, label: &#039;이전 대륙&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+E&#039;, label: &#039;다음 대륙&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return [];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderClbiBottomShortcutGuide() {&lt;br /&gt;
    var guide = document.getElementById(&#039;clbi-bottom-shortcut-guide&#039;);&lt;br /&gt;
    var items;&lt;br /&gt;
    var html;&lt;br /&gt;
&lt;br /&gt;
    if (!guide) return;&lt;br /&gt;
&lt;br /&gt;
    items = getClbiBottomShortcutItems();&lt;br /&gt;
&lt;br /&gt;
    if (!items.length) {&lt;br /&gt;
        guide.classList.add(&#039;is-empty&#039;);&lt;br /&gt;
        guide.innerHTML = &#039;&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    guide.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    html = &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-list&amp;quot;&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        html += &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-key&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.key) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-label&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    html += &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    guide.innerHTML = html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiBottomPlankHtml(wrapId, navId, mainId) {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-bottom-shortcut-guide&amp;quot; class=&amp;quot;is-empty&amp;quot; aria-label=&amp;quot;단축키 안내&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
&lt;br /&gt;
function getClbiNationsTabAtPointer(tabpanel) {&lt;br /&gt;
    var element;&lt;br /&gt;
    var tab;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return null;&lt;br /&gt;
    if (CLBI_NATIONS_LAST_POINTER_X === null || CLBI_NATIONS_LAST_POINTER_Y === null) return null;&lt;br /&gt;
    if (typeof document.elementFromPoint !== &#039;function&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
    element = document.elementFromPoint(CLBI_NATIONS_LAST_POINTER_X, CLBI_NATIONS_LAST_POINTER_Y);&lt;br /&gt;
    if (!element) return null;&lt;br /&gt;
&lt;br /&gt;
    tab = element.closest ? element.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (!tab || !tabpanel.contains(tab)) return null;&lt;br /&gt;
&lt;br /&gt;
    return tab;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateClbiNationsPointerHover(tabpanel, forceSuppress) {&lt;br /&gt;
    var tab = getClbiNationsTabAtPointer(tabpanel);&lt;br /&gt;
    var suppressContinent;&lt;br /&gt;
    var tabContinent;&lt;br /&gt;
    var tabIsActive;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
&lt;br /&gt;
    if (!tab) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabContinent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    tabIsActive = tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (tabIsActive) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    suppressContinent = tabpanel.getAttribute(&#039;data-clbi-hover-suppress-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (forceSuppress || !suppressContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-hover-suppress-continent&#039;, tabContinent);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (suppressContinent === tabContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * The pointer actually moved onto another tab after the keyboard switch.&lt;br /&gt;
     * At that point this is no longer stale browser :hover; let normal hover work.&lt;br /&gt;
     */&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateAllClbiNationsPointerHovers() {&lt;br /&gt;
    var panels = document.querySelectorAll(&#039;.clbi-nations-tabpanel.is-keyboard-switching&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        if (isClbiNationsPanelOwnedTabpanel(tabpanel)) return;&lt;br /&gt;
        validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsPanelOwnedTabpanel(tabpanel) {&lt;br /&gt;
    /*&lt;br /&gt;
     * Mouse continent tab regression guard.&lt;br /&gt;
     * -------------------------------------&lt;br /&gt;
     * Detached SPA preparation serializes attributes/classes but it cannot&lt;br /&gt;
     * serialize DOM event listeners or JS properties.  The NationsPanel fix&lt;br /&gt;
     * therefore uses the DOM property CLBI_NationsPanelOwned /&lt;br /&gt;
     * CLBI_NationsTabPanelBound as the real ownership marker.&lt;br /&gt;
     *&lt;br /&gt;
     * Do NOT treat data-nations-tabpanel-ready or clbi-nations-continent-cache&lt;br /&gt;
     * as ownership here.  Those can survive innerHTML insertion while the real&lt;br /&gt;
     * click listeners were lost, causing Common.js to delegate to a panel that&lt;br /&gt;
     * NationsPanel has not rebound yet.  The fallback below must remain able to&lt;br /&gt;
     * handle mouse clicks until NationsPanel reclaims the live DOM node.&lt;br /&gt;
     */&lt;br /&gt;
    return !!(tabpanel &amp;amp;&amp;amp; tabpanel.CLBI_NationsPanelOwned);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsLiveContinentActive(tabpanel, continent) {&lt;br /&gt;
    var tab;&lt;br /&gt;
    var panel;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !continent) return false;&lt;br /&gt;
&lt;br /&gt;
    tab = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panel = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent-panel&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return !!(&lt;br /&gt;
        tab &amp;amp;&amp;amp;&lt;br /&gt;
        panel &amp;amp;&amp;amp;&lt;br /&gt;
        (tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.classList.contains(&#039;is-active&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.getAttribute(&#039;aria-hidden&#039;) !== &#039;true&#039; &amp;amp;&amp;amp;&lt;br /&gt;
        !panel.hasAttribute(&#039;hidden&#039;)&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function activateClbiNationsContinent(tabpanel, targetContinent, options) {&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var panels;&lt;br /&gt;
    var skipOwner = !!(options &amp;amp;&amp;amp; options.skipOwner);&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !targetContinent) return false;&lt;br /&gt;
&lt;br /&gt;
    if (!skipOwner &amp;amp;&amp;amp; isClbiNationsPanelOwnedTabpanel(tabpanel)) {&lt;br /&gt;
        var owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
&lt;br /&gt;
        if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (owner.activateContinent(targetContinent, {&lt;br /&gt;
                    source: &#039;common-legacy-click&#039;,&lt;br /&gt;
                    panel: tabpanel&lt;br /&gt;
                })) {&lt;br /&gt;
                    return true;&lt;br /&gt;
                }&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Ownership markers can be stale during SPA/hydration edge cases.  If&lt;br /&gt;
         * the owner API is missing or refuses the live panel, do not drop the&lt;br /&gt;
         * mouse click.  Fall through to the local fallback so pointer users are&lt;br /&gt;
         * never left with keyboard-only continent tabs.&lt;br /&gt;
         */&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    panels = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
    if (!tabs.length || !panels.length) return false;&lt;br /&gt;
&lt;br /&gt;
    tabs.forEach(function (tab) {&lt;br /&gt;
        var active = tab.getAttribute(&#039;data-continent&#039;) === targetContinent;&lt;br /&gt;
        tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
        tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panels.forEach(function (panel) {&lt;br /&gt;
        var active = panel.getAttribute(&#039;data-continent-panel&#039;) === targetContinent;&lt;br /&gt;
        panel.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
        if (active) {&lt;br /&gt;
            panel.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            panel.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.sync === &#039;function&#039;) window.CLBI_DECORATIONS.sync();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    validateClbiNationsPointerHover(tabpanel, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
    tabpanel.classList.remove(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
    tabpanel.removeAttribute(&#039;data-clbi-hover-suppress-continent&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveClbiNationsContinent(direction) {&lt;br /&gt;
    var tabpanel = document.querySelector(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var activeIndex;&lt;br /&gt;
    var nextIndex;&lt;br /&gt;
    var target;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return false;&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsPanelOwnedTabpanel(tabpanel) &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI_NATIONS_PANEL &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI_NATIONS_PANEL.moveContinent === &#039;function&#039;) {&lt;br /&gt;
        return !!window.CLBI_NATIONS_PANEL.moveContinent(direction, {&lt;br /&gt;
            source: &#039;common-legacy-keyboard&#039;,&lt;br /&gt;
            panel: tabpanel&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
    activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
        return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
    nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
    target = tabs[nextIndex].getAttribute(&#039;data-continent&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (activateClbiNationsContinent(tabpanel, target)) {&lt;br /&gt;
        setClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
        }, 0);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsTabpanelControls(root) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var panels = scope.querySelectorAll(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        /*&lt;br /&gt;
         * Common.js is only a safety fallback for continent tabs; NationsPanel&lt;br /&gt;
         * is the primary owner.  This guard must be a DOM property, not a&lt;br /&gt;
         * serialized attribute.  SPA warm prepaint can preserve&lt;br /&gt;
         * data-clbi-nations-tabs-ready=&amp;quot;1&amp;quot; while losing the actual event&lt;br /&gt;
         * listeners, which was the root cause of mouse clicks no longer moving&lt;br /&gt;
         * continents while keyboard shortcuts still worked.&lt;br /&gt;
         */&lt;br /&gt;
        if (tabpanel.CLBI_NationsCommonTabsBound) return;&lt;br /&gt;
        tabpanel.CLBI_NationsCommonTabsBound = true;&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-nations-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerdown&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
            if (activateClbiNationsContinent(tabpanel, tab.getAttribute(&#039;data-continent&#039;))) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
            var handled = false;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsContinent(-1);&lt;br /&gt;
            else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsContinent(1);&lt;br /&gt;
&lt;br /&gt;
            if (handled) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                event.stopPropagation();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function handleClbiNationsContinentDocumentClick(event) {&lt;br /&gt;
    var target;&lt;br /&gt;
    var tab;&lt;br /&gt;
    var tabpanel;&lt;br /&gt;
    var continent;&lt;br /&gt;
    var owner;&lt;br /&gt;
    var handled = false;&lt;br /&gt;
&lt;br /&gt;
    if (!event || event.__clbiNationsContinentHandled) return;&lt;br /&gt;
    target = event.target;&lt;br /&gt;
    if (!target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
    tab = target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;);&lt;br /&gt;
    if (!tab) return;&lt;br /&gt;
&lt;br /&gt;
    tabpanel = tab.closest ? tab.closest(&#039;.clbi-nations-tabpanel&#039;) : null;&lt;br /&gt;
    if (!tabpanel || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
    continent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    if (!continent) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Final live-DOM mouse delegate.&lt;br /&gt;
     * --------------------------------&lt;br /&gt;
     * The continent tab UI has two initialization phases: detached preparation&lt;br /&gt;
     * for seamless SPA entry, then live DOM hydration.  Detached preparation can&lt;br /&gt;
     * leave serialized &amp;quot;ready&amp;quot; attributes behind while losing event listeners.&lt;br /&gt;
     * Keyboard shortcuts still work because they call the public NationsPanel&lt;br /&gt;
     * API directly, but mouse clicks depend on live listeners.  This capturing&lt;br /&gt;
     * delegate is intentionally independent of per-panel ready attributes: it&lt;br /&gt;
     * always resolves the clicked live tab and hands it to NationsPanel first,&lt;br /&gt;
     * then falls back to the local Common.js switcher if needed.&lt;br /&gt;
     */&lt;br /&gt;
    event.__clbiNationsContinentHandled = true;&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
    owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
    if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
        try {&lt;br /&gt;
            handled = !!owner.activateContinent(continent, {&lt;br /&gt;
                source: &#039;mouse-click-live-delegate&#039;,&lt;br /&gt;
                panel: tabpanel&lt;br /&gt;
            });&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Owner API stale-cache guard.&lt;br /&gt;
         *&lt;br /&gt;
         * A previous regression came from NationsPanel returning true after it&lt;br /&gt;
         * updated cached/detached tab nodes instead of the clicked live tab. A&lt;br /&gt;
         * true return value alone is therefore not enough for mouse input. Verify&lt;br /&gt;
         * the live DOM that received the click. If it did not become active,&lt;br /&gt;
         * bypass the owner and run the Common.js live query fallback directly.&lt;br /&gt;
         */&lt;br /&gt;
        if (handled &amp;amp;&amp;amp; !isClbiNationsLiveContinentActive(tabpanel, continent)) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!handled) {&lt;br /&gt;
        handled = activateClbiNationsContinent(tabpanel, continent, { skipOwner: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (handled) {&lt;br /&gt;
        try { playStaticSound(); } catch (err2) {}&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) event.stopImmediatePropagation();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsGlobalClickDelegate() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-global-click-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-global-click-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleClbiNationsContinentDocumentClick, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsPointerHoverValidation() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointermove&#039;, function (event) {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = event.clientX;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = event.clientY;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiBottomShortcutSystem(root) {&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsGlobalClickDelegate();&lt;br /&gt;
    initClbiNationsTabpanelControls(root || document);&lt;br /&gt;
    initClbiNationsPointerHoverValidation();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// ── 상·하단 네비게이션 바 ──&lt;br /&gt;
function buildClbiNavHtml(position) {&lt;br /&gt;
    var isBottom = position === &#039;bottom&#039;;&lt;br /&gt;
    var base = isBottom ? &#039;clbi-bottom&#039; : &#039;clbi-top&#039;;&lt;br /&gt;
    var shortBase = isBottom ? &#039;clbi-bnav&#039; : &#039;clbi-tnav&#039;;&lt;br /&gt;
    var wrapId = base + &#039;-nav-wrap&#039;;&lt;br /&gt;
    var navId = base + &#039;-nav&#039;;&lt;br /&gt;
    var mainId = base + &#039;-nav-main&#039;;&lt;br /&gt;
    var tabsId = base + &#039;-nav-tabs&#039;;&lt;br /&gt;
    var searchId = base + &#039;-nav-search&#039;;&lt;br /&gt;
    var inputId = isBottom ? &#039;clbi-bottom-search-input&#039; : &#039;clbi-top-search-input&#039;;&lt;br /&gt;
    var worldId = shortBase + &#039;-worldbuilding&#039;;&lt;br /&gt;
    var subId = isBottom ? &#039;clbi-bottom-sub-worldbuilding&#039; : &#039;clbi-sub-worldbuilding&#039;;&lt;br /&gt;
    var subInnerId = subId + &#039;-inner&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (isBottom) {&lt;br /&gt;
        return buildClbiBottomPlankHtml(wrapId, navId, mainId);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + tabsId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/대문&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-main-menu-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;메인 메뉴&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-project-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;프로젝트&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-top-nav-item&amp;quot; id=&amp;quot;&#039; + worldId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-worldbuilding-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;세계관&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-arrow&amp;quot;&amp;gt;▾&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    (isBottom ? &#039;&#039; : (&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;&#039; + searchId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;&#039; + inputId + &#039;&amp;quot; placeholder=&amp;quot;검색...&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                    )) +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + subId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + subInnerId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-tnav-sub-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/시대&amp;quot;&amp;gt;시대&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/설정&amp;quot;&amp;gt;설정&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/기업_및_공동체&amp;quot;&amp;gt;기업 및 공동체&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/군_정치집단&amp;quot;&amp;gt;군, 정치집단&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/인물&amp;quot;&amp;gt;인물&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeClbiShellDomOrder() {&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var canvas = document.getElementById(&#039;site-halftone-bg&#039;);&lt;br /&gt;
    var anchor;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var topRect;&lt;br /&gt;
    var wrapperRect;&lt;br /&gt;
    var needsRecovery;&lt;br /&gt;
    var expectedWrapperTop;&lt;br /&gt;
    var host;&lt;br /&gt;
&lt;br /&gt;
    if (!contentWrapper || !topNav || !bottomNav || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    CLBI shell can live inside a Liberty &amp;lt;section&amp;gt;.  Some skins/layouts give that&lt;br /&gt;
    section a flow context that lets the top nav visually overlap the content&lt;br /&gt;
    wrapper even when DOM sibling order is correct.  Mark the common parent and&lt;br /&gt;
    let Layout.css force a simple vertical flow for the shell.&lt;br /&gt;
    */&lt;br /&gt;
    host = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp;&lt;br /&gt;
        contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? contentWrapper.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (host) {&lt;br /&gt;
        host.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    viewportH = window.innerHeight || document.documentElement.clientHeight || 0;&lt;br /&gt;
    topRect = topNav.getBoundingClientRect();&lt;br /&gt;
    wrapperRect = contentWrapper.getBoundingClientRect();&lt;br /&gt;
    expectedWrapperTop = topRect.bottom + 8;&lt;br /&gt;
&lt;br /&gt;
    needsRecovery = false;&lt;br /&gt;
&lt;br /&gt;
    if (viewportH &amp;gt; 0) {&lt;br /&gt;
        if (topRect.top &amp;gt;= viewportH * 0.55) needsRecovery = true;&lt;br /&gt;
        if (wrapperRect.top &amp;gt;= viewportH * 0.60) needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (topRect.top &amp;gt; 240 || wrapperRect.top &amp;gt; 320) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (wrapperRect.top &amp;lt; expectedWrapperTop - 1) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!needsRecovery) {&lt;br /&gt;
        document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    anchor = canvas &amp;amp;&amp;amp; canvas.parentNode === document.body&lt;br /&gt;
        ? canvas.nextSibling&lt;br /&gt;
        : document.body.firstChild;&lt;br /&gt;
&lt;br /&gt;
    document.body.insertBefore(topNav, anchor);&lt;br /&gt;
    document.body.insertBefore(contentWrapper, topNav.nextSibling);&lt;br /&gt;
    document.body.insertBefore(bottomNav, contentWrapper.nextSibling);&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.normalizeClbiShellDomOrder = normalizeClbiShellDomOrder;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureClbiVerticalScaleHost() {&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var host;&lt;br /&gt;
    var existing;&lt;br /&gt;
    var parent;&lt;br /&gt;
&lt;br /&gt;
    if (!topNav || !contentWrapper || !bottomNav || !document.body) return null;&lt;br /&gt;
&lt;br /&gt;
    existing = document.getElementById(&#039;clbi-shell-scale-host&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (existing &amp;amp;&amp;amp; existing.contains(topNav) &amp;amp;&amp;amp; existing.contains(contentWrapper) &amp;amp;&amp;amp; existing.contains(bottomNav)) {&lt;br /&gt;
        existing.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        return existing;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    parent = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp; contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? topNav.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (parent &amp;amp;&amp;amp; parent !== document.body) {&lt;br /&gt;
        parent.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        parent.id = parent.id || &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
        return parent;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host = existing || document.createElement(&#039;div&#039;);&lt;br /&gt;
    host.id = &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
    host.className = &#039;clbi-shell-host&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!host.parentNode) {&lt;br /&gt;
        document.body.insertBefore(host, topNav);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host.appendChild(topNav);&lt;br /&gt;
    host.appendChild(contentWrapper);&lt;br /&gt;
    host.appendChild(bottomNav);&lt;br /&gt;
&lt;br /&gt;
    return host;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiRootPx(name, fallback) {&lt;br /&gt;
    var raw = &#039;&#039;;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        raw = getComputedStyle(document.documentElement).getPropertyValue(name);&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    value = parseFloat(raw);&lt;br /&gt;
    return isFinite(value) &amp;amp;&amp;amp; value &amp;gt; 0 ? value : fallback;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiShellVerticalScale() {&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    var host;&lt;br /&gt;
    var topInner;&lt;br /&gt;
    var bottomInner;&lt;br /&gt;
    var thresholdH;&lt;br /&gt;
    var gap;&lt;br /&gt;
    var outerGapTotal;&lt;br /&gt;
    var baseStageH;&lt;br /&gt;
    var stageW;&lt;br /&gt;
    var stageH;&lt;br /&gt;
    var viewportW;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var availableW;&lt;br /&gt;
    var availableH;&lt;br /&gt;
    var topH;&lt;br /&gt;
    var bottomH;&lt;br /&gt;
    var contentH;&lt;br /&gt;
    var widthScale;&lt;br /&gt;
    var heightScale;&lt;br /&gt;
    var scale;&lt;br /&gt;
    var shouldScale;&lt;br /&gt;
&lt;br /&gt;
    if (!root || !body) return;&lt;br /&gt;
&lt;br /&gt;
    host = ensureClbiVerticalScaleHost();&lt;br /&gt;
    if (!host) return;&lt;br /&gt;
&lt;br /&gt;
    thresholdH = readClbiRootPx(&#039;--clbi-vertical-scale-threshold-h&#039;, 1080);&lt;br /&gt;
    gap = readClbiRootPx(&#039;--layout-gap&#039;, 8);&lt;br /&gt;
    outerGapTotal = gap * 2;&lt;br /&gt;
    baseStageH = Math.max(420, thresholdH - outerGapTotal);&lt;br /&gt;
    stageW = readClbiRootPx(&#039;--layout-shell-w&#039;, 1880);&lt;br /&gt;
&lt;br /&gt;
    viewportW = Math.max(320, window.innerWidth || document.documentElement.clientWidth || stageW);&lt;br /&gt;
    viewportH = Math.max(320, window.innerHeight || document.documentElement.clientHeight || thresholdH);&lt;br /&gt;
    availableW = Math.max(240, viewportW - outerGapTotal);&lt;br /&gt;
    availableH = Math.max(240, viewportH - outerGapTotal);&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    평상시에는 기존 세로 채움 레이아웃을 기준으로 삼는다.&lt;br /&gt;
    세로가 기준점보다 작아지는 경우에는 기준 높이를 고정점으로 삼고,&lt;br /&gt;
    가로만 부족한 경우에는 현재 사용 가능한 세로 높이를 고정점으로 삼는다.&lt;br /&gt;
    이렇게 해야 가로 부족으로 scale에 들어갈 때 본문 높이가 갑자기 접히지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    stageH = availableH &amp;gt;= baseStageH ? availableH : baseStageH;&lt;br /&gt;
&lt;br /&gt;
    topInner = document.getElementById(&#039;clbi-top-nav&#039;);&lt;br /&gt;
    bottomInner = document.getElementById(&#039;clbi-bottom-nav&#039;);&lt;br /&gt;
&lt;br /&gt;
    topH = topInner ? Math.ceil(topInner.offsetHeight || topInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    bottomH = bottomInner ? Math.ceil(bottomInner.offsetHeight || bottomInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    contentH = Math.max(360, Math.floor(stageH - topH - bottomH - (gap * 2)));&lt;br /&gt;
&lt;br /&gt;
    widthScale = availableW &amp;lt; stageW ? availableW / stageW : 1;&lt;br /&gt;
    heightScale = availableH &amp;lt; baseStageH ? availableH / baseStageH : 1;&lt;br /&gt;
    scale = Math.min(1, widthScale, heightScale);&lt;br /&gt;
    scale = Math.max(0.50, Math.min(1, Math.floor(scale * 1000) / 1000));&lt;br /&gt;
    shouldScale = scale &amp;lt; 0.999;&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-w&#039;, stageW + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-h&#039;, Math.floor(stageH) + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-content-h&#039;, contentH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-shell-scale&#039;, String(scale));&lt;br /&gt;
&lt;br /&gt;
    /* 대문 canvas 프레임은 문서 셸 배율의 역수를 즉시 공유한다. */&lt;br /&gt;
    if (&lt;br /&gt;
        window.CLBI &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI.frame &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI.frame.setShellScale === &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        window.CLBI.frame.setShellScale(scale);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (window.CategoryNav &amp;amp;&amp;amp; typeof window.CategoryNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                window.CategoryNav.renderAll(document);&lt;br /&gt;
            }&lt;br /&gt;
            if (window.PortalSectionNav &amp;amp;&amp;amp; typeof window.PortalSectionNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                window.PortalSectionNav.renderAll(document);&lt;br /&gt;
            }&lt;br /&gt;
            if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.render === &#039;function&#039;) {&lt;br /&gt;
                window.BottomGuideNav.render();&lt;br /&gt;
            }&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    body.classList.toggle(&#039;clbi-shell-vertical-scale&#039;, shouldScale);&lt;br /&gt;
&lt;br /&gt;
    if (shouldScale) {&lt;br /&gt;
        resetLeftRecentAdaptiveState();&lt;br /&gt;
        resetLeftBillboardAdaptiveState();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
window.updateClbiShellVerticalScale = updateClbiShellVerticalScale;&lt;br /&gt;
&lt;br /&gt;
var $contentWrapper = $(&#039;.content-wrapper&#039;).first();&lt;br /&gt;
&lt;br /&gt;
if ($contentWrapper.length) {&lt;br /&gt;
    $(&#039;#clbi-top-nav-wrap, #clbi-bottom-nav-wrap&#039;).remove();&lt;br /&gt;
    $contentWrapper.before(buildClbiNavHtml(&#039;top&#039;));&lt;br /&gt;
    $contentWrapper.after(buildClbiNavHtml(&#039;bottom&#039;));&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsTabpanelControls(document);&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
function runClbiShellMetricsBatch() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var topH = 0;&lt;br /&gt;
    var bottomH = 0;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    if (top) {&lt;br /&gt;
        topH = Math.ceil(top.getBoundingClientRect().height || top.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (bottom) {&lt;br /&gt;
        bottomH = Math.ceil(bottom.getBoundingClientRect().height || bottom.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-top-nav-outer-h&#039;, topH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-bottom-nav-outer-h&#039;, bottomH + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (typeof updateClbiShellVerticalScale === &#039;function&#039;) {&lt;br /&gt;
        updateClbiShellVerticalScale();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestClbiShellMetricsFrame() {&lt;br /&gt;
    if (CLBI_SHELL_METRICS_RAF !== null) return;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = window.requestAnimationFrame&lt;br /&gt;
        ? window.requestAnimationFrame(runClbiShellMetricsBatch)&lt;br /&gt;
        : window.setTimeout(runClbiShellMetricsBatch, 16);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiShellMetrics() {&lt;br /&gt;
    requestClbiShellMetricsFrame();&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 0);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 80);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 240);&lt;br /&gt;
}&lt;br /&gt;
function watchClbiShellMetrics() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var observer;&lt;br /&gt;
&lt;br /&gt;
    scheduleClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
    $(window).on(&#039;resize orientationchange&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    $(window).on(&#039;pageshow.clbiShellScale focus.clbiShellScale&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
        if (!document.hidden) scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
    $(window).on(&#039;resize.clbiLeftBillboard orientationchange.clbiLeftBillboard&#039;, scheduleLeftSidebarVerticalFit);&lt;br /&gt;
    $(window).on(&#039;resize.clbiRecentViewport orientationchange.clbiRecentViewport&#039;, function () { scheduleAdaptiveLeftRecentItems(); scheduleClbiContentBottomGap(); });&lt;br /&gt;
    $(window).on(&#039;resize.clbiContentBottomGap orientationchange.clbiContentBottomGap&#039;, scheduleClbiContentBottomGap);&lt;br /&gt;
&lt;br /&gt;
    if (window.ResizeObserver) {&lt;br /&gt;
        observer = new ResizeObserver(scheduleClbiShellMetrics);&lt;br /&gt;
        if (top) observer.observe(top);&lt;br /&gt;
        if (bottom) observer.observe(bottom);&lt;br /&gt;
        window.CLBI_SHELL_RESIZE_OBSERVER = observer;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindClbiWorldbuildingToggle(buttonSelector, menuSelector) {&lt;br /&gt;
    $(buttonSelector).on(&#039;click&#039;, function() {&lt;br /&gt;
        var $menu = $(menuSelector);&lt;br /&gt;
        var $btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        $menu.toggleClass(&#039;worldbuilding-open&#039;);&lt;br /&gt;
        $btn.toggleClass(&#039;clbi-tnav-active&#039;, $menu.hasClass(&#039;worldbuilding-open&#039;));&lt;br /&gt;
        scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-tnav-worldbuilding&#039;, &#039;#clbi-sub-worldbuilding&#039;);&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-bnav-worldbuilding&#039;, &#039;#clbi-bottom-sub-worldbuilding&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-top-search-input, #clbi-bottom-search-input&#039;).on(&#039;keydown&#039;, function(e) {&lt;br /&gt;
    if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
        var q = $(this).val().trim();&lt;br /&gt;
        if (q) window.location.href = &#039;/index.php?search=&#039; + encodeURIComponent(q);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
        initClbiBottomShortcutSystem($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
watchClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// 페이지 전환 사운드&lt;br /&gt;
var transitionSound = new Audio(&#039;/index.php?title=특수:Redirect/file/Sfx-ui-001.mp3&#039;);&lt;br /&gt;
&lt;br /&gt;
(function() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
    transitionSound.volume = sfxOn ? master * sfx : 0;&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
function playStaticSound() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!sfxOn) return;&lt;br /&gt;
&lt;br /&gt;
    transitionSound.volume = master * sfx;&lt;br /&gt;
    transitionSound.currentTime = 0;&lt;br /&gt;
    transitionSound.play();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 현재 언어 감지&lt;br /&gt;
function getCurrentLang() {&lt;br /&gt;
    var langData = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    return langData ? (langData.getAttribute(&#039;data-lang&#039;) || &#039;ko&#039;) : &#039;ko&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizePageName(value) {&lt;br /&gt;
    return String(value || &#039;&#039;)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildWikiPath(title) {&lt;br /&gt;
    return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLangShortCode(lang) {&lt;br /&gt;
    var map = { ko: &#039;KR&#039;, en: &#039;EN&#039;, zh: &#039;ZH&#039;, ja: &#039;JA&#039;, ru: &#039;RU&#039;, es: &#039;ES&#039; };&lt;br /&gt;
    return map[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLanguageTargetTitle(lang) {&lt;br /&gt;
    var data = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    if (!data || !lang) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var keys = [&lt;br /&gt;
        &#039;data-&#039; + lang,&lt;br /&gt;
        &#039;data-page-&#039; + lang,&lt;br /&gt;
        &#039;data-title-&#039; + lang,&lt;br /&gt;
        &#039;data-target-&#039; + lang,&lt;br /&gt;
        &#039;data-lang-&#039; + lang&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var value = data.getAttribute(keys[i]);&lt;br /&gt;
        if (value) return value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeClbiHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var SIDEBAR_LANG_SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_TITLE = &#039;MediaWiki:LanguageStatus.json&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;English&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;Русский&#039;,&lt;br /&gt;
    es: &#039;Español&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_DIAL_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;ENG&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;РУС&#039;,&lt;br /&gt;
    es: &#039;ESP&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_VALUES = {&lt;br /&gt;
    available: true,&lt;br /&gt;
    wip: true,&lt;br /&gt;
    unavailable: true&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageStatusRegistry = {};&lt;br /&gt;
var sidebarLanguageStatusLoaded = false;&lt;br /&gt;
var sidebarLanguageStatusLoading = false;&lt;br /&gt;
var sidebarLanguageStatusCallbacks = [];&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageState = {&lt;br /&gt;
    order: [&#039;ko&#039;, &#039;en&#039;, &#039;zh&#039;, &#039;ja&#039;, &#039;ru&#039;, &#039;es&#039;],&lt;br /&gt;
    currentLang: &#039;ko&#039;,&lt;br /&gt;
    baseIndex: 0,&lt;br /&gt;
    selectedIndex: 0,&lt;br /&gt;
    rotation: 0,&lt;br /&gt;
    dragging: false,&lt;br /&gt;
    dragMoved: false,&lt;br /&gt;
    dragStartX: 0,&lt;br /&gt;
    dragStartY: 0,&lt;br /&gt;
    dragStartRotation: 0,&lt;br /&gt;
    dragAxis: null,&lt;br /&gt;
    pointerCaptured: false,&lt;br /&gt;
    lastX: 0,&lt;br /&gt;
    lastTime: 0,&lt;br /&gt;
    releaseVelocity: 0,&lt;br /&gt;
    suppressClickUntil: 0,&lt;br /&gt;
    raf: null,&lt;br /&gt;
    pendingRotation: 0,&lt;br /&gt;
    snapTimer: null,&lt;br /&gt;
    inertiaRaf: null,&lt;br /&gt;
    navigateTimer: null,&lt;br /&gt;
    bound: false,&lt;br /&gt;
    boundElement: null,&lt;br /&gt;
    rotor: null,&lt;br /&gt;
    cx: 101,&lt;br /&gt;
    cy: 119,&lt;br /&gt;
    outerR: 109,&lt;br /&gt;
    innerR: 28,&lt;br /&gt;
    sectorAngle: 30,&lt;br /&gt;
    halfSector: 15,&lt;br /&gt;
    repeats: 8,&lt;br /&gt;
    dragSensitivity: 0.58,&lt;br /&gt;
    maxSpinVelocity: 1.75,&lt;br /&gt;
    minSpinVelocity: 0.055,&lt;br /&gt;
    spinDecel: 0.00185&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function createSidebarLanguageSvgEl(tag) {&lt;br /&gt;
    return document.createElementNS(SIDEBAR_LANG_SVG_NS, tag);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageIndex(index) {&lt;br /&gt;
    var length = sidebarLanguageState.order.length;&lt;br /&gt;
    var normalized = index % length;&lt;br /&gt;
    return normalized &amp;lt; 0 ? normalized + length : normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageDialName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_DIAL_LABELS[lang] || getSidebarLanguageName(lang);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageStatusValue(value) {&lt;br /&gt;
    value = String(value == null ? &#039;&#039; : value).toLowerCase().trim();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_STATUS_VALUES[value] ? value : &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusPageKey() {&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
&lt;br /&gt;
    return normalized || raw || &#039;대문&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusEntry() {&lt;br /&gt;
    var registry = sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    var pages = registry.pages &amp;amp;&amp;amp; typeof registry.pages === &#039;object&#039; ? registry.pages : registry;&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
    var title = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var keys = [&lt;br /&gt;
        normalized,&lt;br /&gt;
        raw,&lt;br /&gt;
        raw.replace(/_/g, &#039; &#039;),&lt;br /&gt;
        normalized.replace(/ /g, &#039;_&#039;),&lt;br /&gt;
        title,&lt;br /&gt;
        title.replace(/_/g, &#039; &#039;)&lt;br /&gt;
    ];&lt;br /&gt;
    var i;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; keys.length; i += 1) {&lt;br /&gt;
        if (keys[i] &amp;amp;&amp;amp; pages[keys[i]] &amp;amp;&amp;amp; typeof pages[keys[i]] === &#039;object&#039;) {&lt;br /&gt;
            return pages[keys[i]];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {};&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusOverride(lang) {&lt;br /&gt;
    var entry = getSidebarLanguageStatusEntry();&lt;br /&gt;
    return normalizeSidebarLanguageStatusValue(entry[lang]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function flushSidebarLanguageStatusCallbacks() {&lt;br /&gt;
    var callbacks = sidebarLanguageStatusCallbacks.slice();&lt;br /&gt;
    sidebarLanguageStatusCallbacks.length = 0;&lt;br /&gt;
&lt;br /&gt;
    callbacks.forEach(function(callback) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
            callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadSidebarLanguageStatusRegistry(callback, force) {&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        sidebarLanguageStatusCallbacks.push(callback);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoaded &amp;amp;&amp;amp; !force) {&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoading) return;&lt;br /&gt;
&lt;br /&gt;
    sidebarLanguageStatusLoading = true;&lt;br /&gt;
&lt;br /&gt;
    function finishLanguageStatus(parsed) {&lt;br /&gt;
        sidebarLanguageStatusRegistry = parsed &amp;amp;&amp;amp; typeof parsed === &#039;object&#039; ? parsed : {};&lt;br /&gt;
        sidebarLanguageStatusLoaded = true;&lt;br /&gt;
        sidebarLanguageStatusLoading = false;&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!force &amp;amp;&amp;amp; window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.fetchJsonRef === &#039;function&#039;) {&lt;br /&gt;
        window.EntryStore.fetchJsonRef(SIDEBAR_LANGUAGE_STATUS_TITLE, { noStore: false })&lt;br /&gt;
            .then(function (parsed) { finishLanguageStatus(parsed); })&lt;br /&gt;
            .catch(function () { finishLanguageStatus({}); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    (function () {&lt;br /&gt;
        var url = mw.util.getUrl(SIDEBAR_LANGUAGE_STATUS_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, SIDEBAR_LANGUAGE_STATUS_TITLE);&lt;br /&gt;
        }&lt;br /&gt;
        $.ajax({&lt;br /&gt;
            url: url,&lt;br /&gt;
            dataType: &#039;text&#039;,&lt;br /&gt;
            cache: true&lt;br /&gt;
        }).done(function(text) {&lt;br /&gt;
            var parsed = {};&lt;br /&gt;
&lt;br /&gt;
            try {&lt;br /&gt;
                parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                console.error(&#039;LanguageStatus.json parse failed:&#039;, err);&lt;br /&gt;
                parsed = {};&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            finishLanguageStatus(parsed);&lt;br /&gt;
        }).fail(function() {&lt;br /&gt;
            finishLanguageStatus({});&lt;br /&gt;
        });&lt;br /&gt;
    })();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.CLBI_LANGUAGE_STATUS = {&lt;br /&gt;
    title: SIDEBAR_LANGUAGE_STATUS_TITLE,&lt;br /&gt;
    languages: sidebarLanguageState.order.slice(),&lt;br /&gt;
    labels: SIDEBAR_LANGUAGE_LABELS,&lt;br /&gt;
    dialLabels: SIDEBAR_LANGUAGE_DIAL_LABELS,&lt;br /&gt;
    getPageKey: getSidebarLanguageStatusPageKey,&lt;br /&gt;
    getRegistry: function() {&lt;br /&gt;
        return sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    },&lt;br /&gt;
    reload: function(callback) {&lt;br /&gt;
        sidebarLanguageStatusLoaded = false;&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            renderSidebarLanguageBox();&lt;br /&gt;
            if (typeof callback === &#039;function&#039;) callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }, true);&lt;br /&gt;
    },&lt;br /&gt;
    refreshDial: function() {&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageMeta(lang) {&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var targetTitle = getLanguageTargetTitle(lang);&lt;br /&gt;
    var isCurrent = lang === currentLang;&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        lang: lang,&lt;br /&gt;
        code: getLangShortCode(lang),&lt;br /&gt;
        name: getSidebarLanguageName(lang),&lt;br /&gt;
        dialName: getSidebarLanguageDialName(lang),&lt;br /&gt;
        targetTitle: targetTitle,&lt;br /&gt;
        isCurrent: isCurrent,&lt;br /&gt;
        canMove: !!targetTitle &amp;amp;&amp;amp; !isCurrent&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatus(meta) {&lt;br /&gt;
    var override;&lt;br /&gt;
&lt;br /&gt;
    if (!meta) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (meta.isCurrent) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-current&#039;,&lt;br /&gt;
            label: &#039;CURRENT&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    override = getSidebarLanguageStatusOverride(meta.lang);&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;wip&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;WIP&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;unavailable&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;available&#039; || meta.targetTitle) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: meta.targetTitle ? &#039;is-ready&#039; : &#039;is-locked&#039;,&lt;br /&gt;
            label: meta.targetTitle ? &#039;AVAILABLE&#039; : &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: !!meta.targetTitle&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        className: &#039;is-locked&#039;,&lt;br /&gt;
        label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
        canApply: false&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguageRad(deg) {&lt;br /&gt;
    return (deg * Math.PI) / 180;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguagePointAt(radius, deg) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var angle = sidebarLanguageRad(deg);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        x: state.cx + Math.sin(angle) * radius,&lt;br /&gt;
        y: state.cy - Math.cos(angle) * radius&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageSectorPath(start, end) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var p1 = sidebarLanguagePointAt(state.outerR, start);&lt;br /&gt;
    var p2 = sidebarLanguagePointAt(state.outerR, end);&lt;br /&gt;
    var p3 = sidebarLanguagePointAt(state.innerR, end);&lt;br /&gt;
    var p4 = sidebarLanguagePointAt(state.innerR, start);&lt;br /&gt;
    var largeArc = Math.abs(end - start) &amp;gt; 180 ? 1 : 0;&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;M&#039;, p1.x.toFixed(3), p1.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.outerR, state.outerR, 0, largeArc, 1, p2.x.toFixed(3), p2.y.toFixed(3),&lt;br /&gt;
        &#039;L&#039;, p3.x.toFixed(3), p3.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.innerR, state.innerR, 0, largeArc, 0, p4.x.toFixed(3), p4.y.toFixed(3),&lt;br /&gt;
        &#039;Z&#039;&lt;br /&gt;
    ].join(&#039; &#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageShellPath() {&lt;br /&gt;
    return getSidebarLanguageSectorPath(-68, 68);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageByStep(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        index: index,&lt;br /&gt;
        meta: getSidebarLanguageMeta(state.order[index])&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewIndex() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    return normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewMeta() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    return getSidebarLanguageMeta(state.order[getSidebarLanguagePreviewIndex()]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeSidebarLanguageSector(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var item = getSidebarLanguageByStep(step);&lt;br /&gt;
    var group = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    var path = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    var label = createSidebarLanguageSvgEl(&#039;text&#039;);&lt;br /&gt;
    var labelY = state.cy - 78;&lt;br /&gt;
    var angle = step * state.sectorAngle;&lt;br /&gt;
&lt;br /&gt;
    group.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-group&#039;);&lt;br /&gt;
    group.setAttribute(&#039;data-step&#039;, String(step));&lt;br /&gt;
    group.setAttribute(&#039;data-index&#039;, String(item.index));&lt;br /&gt;
    group.setAttribute(&#039;data-lang&#039;, item.meta.lang);&lt;br /&gt;
    group.setAttribute(&#039;transform&#039;, &#039;rotate(&#039; + angle + &#039; &#039; + state.cx + &#039; &#039; + state.cy + &#039;)&#039;);&lt;br /&gt;
&lt;br /&gt;
    path.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector&#039;);&lt;br /&gt;
    path.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
&lt;br /&gt;
    label.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-label&#039;);&lt;br /&gt;
    label.setAttribute(&#039;x&#039;, String(state.cx));&lt;br /&gt;
    label.setAttribute(&#039;y&#039;, String(labelY + 5));&lt;br /&gt;
    label.textContent = item.meta.dialName || item.meta.name;&lt;br /&gt;
&lt;br /&gt;
    group.appendChild(path);&lt;br /&gt;
    group.appendChild(label);&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        if (sidebarLanguageState.dragging || performance.now() &amp;lt; sidebarLanguageState.suppressClickUntil) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        snapSidebarLanguageToStep(parseInt(group.getAttribute(&#039;data-step&#039;) || &#039;0&#039;, 10), true);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return group;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageWheel() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var svg;&lt;br /&gt;
    var defs;&lt;br /&gt;
    var clip;&lt;br /&gt;
    var clipPath;&lt;br /&gt;
    var shadowBlur;&lt;br /&gt;
    var blur;&lt;br /&gt;
    var fixedDepthGradient;&lt;br /&gt;
    var shell;&lt;br /&gt;
    var clipped;&lt;br /&gt;
    var rotor;&lt;br /&gt;
    var fixedDepthPath;&lt;br /&gt;
    var fixedFocus;&lt;br /&gt;
    var shadowSoft;&lt;br /&gt;
    var shadowHard;&lt;br /&gt;
    var rim;&lt;br /&gt;
    var pointer;&lt;br /&gt;
    var tri;&lt;br /&gt;
    var line;&lt;br /&gt;
    var step;&lt;br /&gt;
&lt;br /&gt;
    if (!fan) return;&lt;br /&gt;
&lt;br /&gt;
    fan.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    svg = createSidebarLanguageSvgEl(&#039;svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fan-svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 202 150&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;xMidYMid meet&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;role&#039;, &#039;img&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;aria-label&#039;, &#039;언어 선택 다이얼&#039;);&lt;br /&gt;
&lt;br /&gt;
    defs = createSidebarLanguageSvgEl(&#039;defs&#039;);&lt;br /&gt;
&lt;br /&gt;
    clip = createSidebarLanguageSvgEl(&#039;clipPath&#039;);&lt;br /&gt;
    clip.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fan-clip&#039;);&lt;br /&gt;
    clipPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    clipPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    clip.appendChild(clipPath);&lt;br /&gt;
&lt;br /&gt;
    shadowBlur = createSidebarLanguageSvgEl(&#039;filter&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-shadow-blur&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;x&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;y&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;width&#039;, &#039;140%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;height&#039;, &#039;140%&#039;);&lt;br /&gt;
    blur = createSidebarLanguageSvgEl(&#039;feGaussianBlur&#039;);&lt;br /&gt;
    blur.setAttribute(&#039;stdDeviation&#039;, &#039;3&#039;);&lt;br /&gt;
    shadowBlur.appendChild(blur);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthGradient = createSidebarLanguageSvgEl(&#039;linearGradient&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x2&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y2&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    [&lt;br /&gt;
        [&#039;0%&#039;, &#039;#ffffff&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;34%&#039;, &#039;#ffffff&#039;, &#039;0.006&#039;],&lt;br /&gt;
        [&#039;58%&#039;, &#039;#000000&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;100%&#039;, &#039;#000000&#039;, &#039;0.250&#039;]&lt;br /&gt;
    ].forEach(function(item) {&lt;br /&gt;
        var stop = createSidebarLanguageSvgEl(&#039;stop&#039;);&lt;br /&gt;
        stop.setAttribute(&#039;offset&#039;, item[0]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-color&#039;, item[1]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-opacity&#039;, item[2]);&lt;br /&gt;
        fixedDepthGradient.appendChild(stop);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    defs.appendChild(clip);&lt;br /&gt;
    defs.appendChild(shadowBlur);&lt;br /&gt;
    defs.appendChild(fixedDepthGradient);&lt;br /&gt;
    svg.appendChild(defs);&lt;br /&gt;
&lt;br /&gt;
    shell = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;class&#039;, &#039;sidebar-lang-shell&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shell);&lt;br /&gt;
&lt;br /&gt;
    clipped = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    clipped.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    rotor = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;class&#039;, &#039;sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
&lt;br /&gt;
    for (step = -state.repeats; step &amp;lt;= state.repeats; step += 1) {&lt;br /&gt;
        rotor.appendChild(makeSidebarLanguageSector(step));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    clipped.appendChild(rotor);&lt;br /&gt;
    svg.appendChild(clipped);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(fixedDepthPath);&lt;br /&gt;
&lt;br /&gt;
    fixedFocus = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-focus&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
    svg.appendChild(fixedFocus);&lt;br /&gt;
&lt;br /&gt;
    shadowSoft = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-soft&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowSoft);&lt;br /&gt;
&lt;br /&gt;
    shadowHard = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-hard&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowHard);&lt;br /&gt;
&lt;br /&gt;
    rim = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;class&#039;, &#039;sidebar-lang-rim&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(rim);&lt;br /&gt;
&lt;br /&gt;
    pointer = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-pointer&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    tri = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-triangle&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;d&#039;, &#039;M &#039; + (state.cx - 10) + &#039; 10 L &#039; + (state.cx + 10) + &#039; 10 L &#039; + state.cx + &#039; 26 Z&#039;);&lt;br /&gt;
    pointer.appendChild(tri);&lt;br /&gt;
&lt;br /&gt;
    line = createSidebarLanguageSvgEl(&#039;line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;x1&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;x2&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;y1&#039;, &#039;24&#039;);&lt;br /&gt;
    line.setAttribute(&#039;y2&#039;, &#039;112&#039;);&lt;br /&gt;
    pointer.appendChild(line);&lt;br /&gt;
&lt;br /&gt;
    svg.appendChild(pointer);&lt;br /&gt;
    fan.appendChild(svg);&lt;br /&gt;
&lt;br /&gt;
    state.rotor = rotor;&lt;br /&gt;
    setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateSidebarLanguageDial() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var meta = getSidebarLanguagePreviewMeta();&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
    var selectedValue = document.getElementById(&#039;clbi-sidebar-lang-selected-value&#039;);&lt;br /&gt;
    var availabilityPanel = document.getElementById(&#039;clbi-sidebar-lang-availability-panel&#039;);&lt;br /&gt;
    var availabilityValue = document.getElementById(&#039;clbi-sidebar-lang-availability-value&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (selectedValue) {&lt;br /&gt;
        selectedValue.textContent = meta.name;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityPanel) {&lt;br /&gt;
        availabilityPanel.classList.remove(&#039;is-ready&#039;, &#039;is-current&#039;, &#039;is-locked&#039;);&lt;br /&gt;
        availabilityPanel.classList.add(status.className);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityValue) {&lt;br /&gt;
        availabilityValue.textContent = status.label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (apply) {&lt;br /&gt;
        apply.classList.toggle(&#039;is-disabled&#039;, !status.canApply);&lt;br /&gt;
        apply.setAttribute(&#039;aria-disabled&#039;, status.canApply ? &#039;false&#039; : &#039;true&#039;);&lt;br /&gt;
        apply.setAttribute(&#039;aria-label&#039;, status.canApply ? (meta.name + &#039; 적용&#039;) : (meta.isCurrent ? &#039;현재 언어&#039; : &#039;사용할 수 없는 언어&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (selector) {&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-lang&#039;, meta.lang);&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-code&#039;, meta.code);&lt;br /&gt;
        selector.classList.toggle(&#039;is-current&#039;, meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-ready&#039;, status.canApply);&lt;br /&gt;
        selector.classList.toggle(&#039;is-locked&#039;, !status.canApply &amp;amp;&amp;amp; !meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-dragging&#039;, !!state.dragging);&lt;br /&gt;
        selector.classList.toggle(&#039;is-spinning&#039;, !!state.inertiaRaf);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        meta: meta,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageRotation(value, animate) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.rotation = value;&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (!state.rotor) return;&lt;br /&gt;
&lt;br /&gt;
    if (animate) {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-snapping&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.rotor.style.transform = &#039;rotate(&#039; + state.rotation.toFixed(3) + &#039;deg)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestSidebarLanguageRotation(value) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.pendingRotation = value;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) return;&lt;br /&gt;
&lt;br /&gt;
    state.raf = requestAnimationFrame(function() {&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
        setSidebarLanguageRotation(state.pendingRotation, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function cancelSidebarLanguageSpin() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    if (state.inertiaRaf) {&lt;br /&gt;
        cancelAnimationFrame(state.inertiaRaf);&lt;br /&gt;
        state.inertiaRaf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finishSidebarLanguageSnap(nearestIndex, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    state.baseIndex = normalizeSidebarLanguageIndex(nearestIndex);&lt;br /&gt;
    state.selectedIndex = state.baseIndex;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping is-dragging is-spinning&#039;);&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        callback(getSidebarLanguageMeta(state.order[state.selectedIndex]));&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageToStep(step, animate, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var targetRotation = -step * state.sectorAngle;&lt;br /&gt;
    var nearestIndex = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.selectedIndex = nearestIndex;&lt;br /&gt;
    setSidebarLanguageRotation(targetRotation, !!animate);&lt;br /&gt;
&lt;br /&gt;
    state.snapTimer = setTimeout(function() {&lt;br /&gt;
        finishSidebarLanguageSnap(nearestIndex, callback);&lt;br /&gt;
    }, animate ? 230 : 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageNearest(callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    snapSidebarLanguageToStep(step, true, callback);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function startSidebarLanguageInertiaSpin(initialVelocity) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var velocity;&lt;br /&gt;
    var lastFrame;&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
&lt;br /&gt;
    velocity = Math.max(-state.maxSpinVelocity, Math.min(state.maxSpinVelocity, initialVelocity));&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(velocity) &amp;lt; state.minSpinVelocity) {&lt;br /&gt;
        snapSidebarLanguageNearest();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-spinning&#039;);&lt;br /&gt;
    lastFrame = performance.now();&lt;br /&gt;
&lt;br /&gt;
    function frame(now) {&lt;br /&gt;
        var dt = Math.min(34, Math.max(1, now - lastFrame));&lt;br /&gt;
        var sign = velocity &amp;lt; 0 ? -1 : 1;&lt;br /&gt;
        var nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        state.rotation += velocity * dt;&lt;br /&gt;
        setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
&lt;br /&gt;
        nextSpeed = Math.max(0, Math.abs(velocity) - (state.spinDecel * dt));&lt;br /&gt;
        velocity = sign * nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        if (nextSpeed &amp;lt;= state.minSpinVelocity) {&lt;br /&gt;
            state.inertiaRaf = null;&lt;br /&gt;
            $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleSidebarLanguageNavigation(meta) {&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
&lt;br /&gt;
    if (!meta || !status.canApply) return;&lt;br /&gt;
&lt;br /&gt;
    clearTimeout(sidebarLanguageState.navigateTimer);&lt;br /&gt;
    sidebarLanguageState.navigateTimer = setTimeout(function() {&lt;br /&gt;
        var title = getLanguageTargetTitle(meta.lang);&lt;br /&gt;
&lt;br /&gt;
        if (!title || meta.lang === getCurrentLang()) return;&lt;br /&gt;
&lt;br /&gt;
        window.location.href = buildWikiPath(title);&lt;br /&gt;
    }, 70);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageSelection(lang) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = state.order.indexOf(lang);&lt;br /&gt;
&lt;br /&gt;
    if (index &amp;lt; 0) index = state.order.indexOf(getCurrentLang());&lt;br /&gt;
    if (index &amp;lt; 0) index = 0;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) {&lt;br /&gt;
        cancelAnimationFrame(state.raf);&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.currentLang = lang;&lt;br /&gt;
    state.baseIndex = index;&lt;br /&gt;
    state.selectedIndex = index;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
    state.dragMoved = false;&lt;br /&gt;
    state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveSidebarLanguageSelection(delta) {&lt;br /&gt;
    snapSidebarLanguageToStep(-delta, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindSidebarLanguageSelector() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!selector || !fan || !apply) return;&lt;br /&gt;
&lt;br /&gt;
    if (state.bound &amp;amp;&amp;amp; state.boundElement === selector) return;&lt;br /&gt;
&lt;br /&gt;
    state.bound = true;&lt;br /&gt;
    state.boundElement = selector;&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerdown&#039;, function(e) {&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
        state.dragging = true;&lt;br /&gt;
        state.dragMoved = false;&lt;br /&gt;
        state.dragStartX = e.clientX;&lt;br /&gt;
        state.dragStartY = e.clientY || 0;&lt;br /&gt;
        state.dragStartRotation = state.rotation;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = performance.now();&lt;br /&gt;
        state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
        selector.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
        selector.classList.remove(&#039;is-snapping&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        Vertical page scrolling must stay available when the pointer starts on&lt;br /&gt;
        the language dial. Capture and preventDefault are delayed until a&lt;br /&gt;
        horizontal drag is confirmed.&lt;br /&gt;
        */&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointermove&#039;, function(e) {&lt;br /&gt;
        var now;&lt;br /&gt;
        var totalDx;&lt;br /&gt;
        var totalDy;&lt;br /&gt;
        var frameDx;&lt;br /&gt;
        var dt;&lt;br /&gt;
        var instantVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        totalDx = e.clientX - state.dragStartX;&lt;br /&gt;
        totalDy = (e.clientY || 0) - state.dragStartY;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragAxis &amp;amp;&amp;amp; (Math.abs(totalDx) &amp;gt; 4 || Math.abs(totalDy) &amp;gt; 4)) {&lt;br /&gt;
            state.dragAxis = Math.abs(totalDx) &amp;gt;= Math.abs(totalDy) ? &#039;x&#039; : &#039;y&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (state.dragAxis === &#039;y&#039;) {&lt;br /&gt;
                state.dragging = false;&lt;br /&gt;
                state.dragMoved = false;&lt;br /&gt;
                state.dragAxis = null;&lt;br /&gt;
                state.pointerCaptured = false;&lt;br /&gt;
                selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (fan.setPointerCapture &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
                try {&lt;br /&gt;
                    fan.setPointerCapture(e.pointerId);&lt;br /&gt;
                    state.pointerCaptured = true;&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    state.pointerCaptured = false;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragAxis !== &#039;x&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        now = performance.now();&lt;br /&gt;
        frameDx = e.clientX - state.lastX;&lt;br /&gt;
        dt = Math.max(1, now - state.lastTime);&lt;br /&gt;
&lt;br /&gt;
        if (Math.abs(totalDx) &amp;gt; 3) state.dragMoved = true;&lt;br /&gt;
&lt;br /&gt;
        instantVelocity = (frameDx * state.dragSensitivity) / dt;&lt;br /&gt;
        state.releaseVelocity = (state.releaseVelocity * 0.62) + (instantVelocity * 0.38);&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = now;&lt;br /&gt;
&lt;br /&gt;
        requestSidebarLanguageRotation(state.dragStartRotation + totalDx * state.dragSensitivity);&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function finishDrag(e) {&lt;br /&gt;
        var velocityAge;&lt;br /&gt;
        var throwVelocity;&lt;br /&gt;
        var wasHorizontal;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        wasHorizontal = state.dragAxis === &#039;x&#039;;&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (fan.releasePointerCapture &amp;amp;&amp;amp; state.pointerCaptured &amp;amp;&amp;amp; e &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
            try { fan.releasePointerCapture(e.pointerId); } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
&lt;br /&gt;
        if (!wasHorizontal &amp;amp;&amp;amp; !state.dragMoved) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        velocityAge = performance.now() - state.lastTime;&lt;br /&gt;
        throwVelocity = velocityAge &amp;gt; 120 ? 0 : state.releaseVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(throwVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            startSidebarLanguageInertiaSpin(throwVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerup&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;pointercancel&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;lostpointercapture&#039;, function() {&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(state.releaseVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
            startSidebarLanguageInertiaSpin(state.releaseVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    apply.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        snapSidebarLanguageNearest(function(meta) {&lt;br /&gt;
            scheduleSidebarLanguageNavigation(meta);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    selector.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
        if (e.key === &#039;ArrowLeft&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(-1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;ArrowRight&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;Enter&#039; || e.key === &#039; &#039;) {&lt;br /&gt;
            apply.click();&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageBox() {&lt;br /&gt;
    bindSidebarLanguageSelector();&lt;br /&gt;
    setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
&lt;br /&gt;
    if (!sidebarLanguageStatusLoaded) {&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadRecentChangesList(targetSelector, limit) {&lt;br /&gt;
    var $target = $(targetSelector);&lt;br /&gt;
&lt;br /&gt;
    if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
    var isNewsList = $target.closest(&#039;.clbi-left-news-box&#039;).length &amp;gt; 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.html((t &amp;amp;&amp;amp; t.loading) ? t.loading : &#039;불러오는 중...&#039;);&lt;br /&gt;
&lt;br /&gt;
    $.getJSON(&lt;br /&gt;
        &#039;/api.php?action=query&amp;amp;list=recentchanges&amp;amp;rclimit=&#039; + encodeURIComponent(limit || 5) + &#039;&amp;amp;rcprop=title|timestamp|user&amp;amp;format=json&amp;amp;rcnamespace=0&amp;amp;rctype=edit|new&#039;,&lt;br /&gt;
        function(data) {&lt;br /&gt;
            var items = data &amp;amp;&amp;amp; data.query ? data.query.recentchanges : [];&lt;br /&gt;
            var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (!items || !items.length) {&lt;br /&gt;
                $target.html(&#039;표시할 변경 사항이 없습니다.&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $.each(items, function(i, item) {&lt;br /&gt;
                var label = timeAgo(item.timestamp);&lt;br /&gt;
                var title = item.title || &#039;&#039;;&lt;br /&gt;
                var userName = item.user || &#039;Unknown&#039;;&lt;br /&gt;
                var pageHref = buildWikiPath(title);&lt;br /&gt;
                var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(userName) + &#039;.png&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (isNewsList) {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;news-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;news-recent-avatar&amp;quot; src=&amp;quot;&#039; + escapeHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;news-recent-main&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-meta&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-user&amp;quot;&amp;gt;@&#039; + escapeHtml(userName) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
                } else {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;clbi-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;clbi-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList) {&lt;br /&gt;
                $target.html(&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-recent-viewport&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-recent-stack&amp;quot;&amp;gt;&#039; + html + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
                    ensureNewsBottomFinish();&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                $target.html(html);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList &amp;amp;&amp;amp; typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
                scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $target.find(isNewsList ? &#039;.news-recent-item&#039; : &#039;.clbi-recent-item&#039;).each(function() {&lt;br /&gt;
                var wrap = $(this).find(isNewsList ? &#039;.news-recent-title-wrap&#039; : &#039;.clbi-recent-title-wrap&#039;);&lt;br /&gt;
                var title = $(this).find(isNewsList ? &#039;.news-recent-title&#039; : &#039;.clbi-recent-title&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (!wrap.length || !title.length) return;&lt;br /&gt;
&lt;br /&gt;
                var wrapW = wrap.width();&lt;br /&gt;
                var titleW = title[0].scrollWidth;&lt;br /&gt;
&lt;br /&gt;
                if (titleW &amp;gt; wrapW + 20) {&lt;br /&gt;
                    var duration = titleW / 40;&lt;br /&gt;
&lt;br /&gt;
                    title.css({&lt;br /&gt;
                        animation: &#039;clbi-scroll &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
                        &#039;--scroll-dist&#039;: &#039;-&#039; + (titleW - wrapW + 8) + &#039;px&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    ).fail(function() {&lt;br /&gt;
        var lang = getCurrentLang();&lt;br /&gt;
        var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
&lt;br /&gt;
        $target.html((t &amp;amp;&amp;amp; t.loadFail) ? t.loadFail : &#039;불러오기 실패&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureRecentViewport() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var viewport;&lt;br /&gt;
    var stack;&lt;br /&gt;
    var children;&lt;br /&gt;
&lt;br /&gt;
    if (!list) return null;&lt;br /&gt;
&lt;br /&gt;
    viewport = list.querySelector(&#039;:scope &amp;gt; .news-recent-viewport&#039;);&lt;br /&gt;
    stack = viewport ? viewport.querySelector(&#039;:scope &amp;gt; .news-recent-stack&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (viewport &amp;amp;&amp;amp; stack) return viewport;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(list.children || []);&lt;br /&gt;
&lt;br /&gt;
    viewport = document.createElement(&#039;div&#039;);&lt;br /&gt;
    viewport.className = &#039;news-recent-viewport&#039;;&lt;br /&gt;
&lt;br /&gt;
    stack = document.createElement(&#039;div&#039;);&lt;br /&gt;
    stack.className = &#039;news-recent-stack&#039;;&lt;br /&gt;
&lt;br /&gt;
    children.forEach(function (child) {&lt;br /&gt;
        if (child.classList &amp;amp;&amp;amp; child.classList.contains(&#039;news-recent-viewport&#039;)) return;&lt;br /&gt;
        stack.appendChild(child);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    viewport.appendChild(stack);&lt;br /&gt;
    list.appendChild(viewport);&lt;br /&gt;
&lt;br /&gt;
    return viewport;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ensureNewsBottomFinish() {&lt;br /&gt;
    var newsBox = document.querySelector(&#039;#clbi-left-sidebar .clbi-left-news-box&#039;);&lt;br /&gt;
    var content = newsBox ? newsBox.querySelector(&#039;.clbi-news-box&#039;) : null;&lt;br /&gt;
    var finish;&lt;br /&gt;
&lt;br /&gt;
    if (!content) return null;&lt;br /&gt;
&lt;br /&gt;
    finish = content.querySelector(&#039;:scope &amp;gt; .news-bottom-finish&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!finish) {&lt;br /&gt;
        finish = document.createElement(&#039;div&#039;);&lt;br /&gt;
        finish.className = &#039;news-bottom-finish&#039;;&lt;br /&gt;
        finish.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        content.appendChild(finish);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return finish;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateAdaptiveLeftRecentItems() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: 좌측 사이드는 뉴스 확장형 flex 레이아웃이 높이를 담당한다.&lt;br /&gt;
    이전 adaptive 코드는 항목을 숨기거나 mask/fade를 걸기 위한 것이었으므로&lt;br /&gt;
    여기서는 잔여 상태만 정리하고 DOM 래퍼만 보장한다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftRecentAdaptiveState();&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureRecentViewport === &#039;function&#039;) {&lt;br /&gt;
        ensureRecentViewport();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
        ensureNewsBottomFinish();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleAdaptiveLeftRecentItems() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateAdaptiveLeftRecentItems();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 80);&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateClbiContentBottomGap(iteration) {&lt;br /&gt;
    var content = document.querySelector(&#039;.container-fluid.liberty-content&#039;);&lt;br /&gt;
    var main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var desiredGap = 8;&lt;br /&gt;
    var rootStyle;&lt;br /&gt;
    var scale = 1;&lt;br /&gt;
    var contentRect;&lt;br /&gt;
    var bottomTop;&lt;br /&gt;
    var targetHeight;&lt;br /&gt;
    var currentHeight;&lt;br /&gt;
    var visualGap;&lt;br /&gt;
&lt;br /&gt;
    iteration = iteration || 0;&lt;br /&gt;
&lt;br /&gt;
    if (!content || !main || !bottomNav) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    하단 간격은 scale 모드에서도 같은 기준으로 계산한다.&lt;br /&gt;
    transform:scale()이 걸리면 getBoundingClientRect()는 축소된 화면 좌표를 반환하므로,&lt;br /&gt;
    목표 간격 8px도 scale을 곱한 화면 좌표로 비교하고 다시 design px로 환산한다.&lt;br /&gt;
&lt;br /&gt;
    목표:&lt;br /&gt;
    .liberty-content-main.bottom === #clbi-bottom-nav-wrap.top - 8px&lt;br /&gt;
    */&lt;br /&gt;
    if (document.body &amp;amp;&amp;amp; document.body.classList &amp;amp;&amp;amp; document.body.classList.contains(&#039;clbi-shell-vertical-scale&#039;)) {&lt;br /&gt;
        rootStyle = window.getComputedStyle(document.documentElement);&lt;br /&gt;
        scale = parseFloat(rootStyle.getPropertyValue(&#039;--clbi-shell-scale&#039;)) || 1;&lt;br /&gt;
        scale = Math.max(0.25, scale);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    contentRect = content.getBoundingClientRect();&lt;br /&gt;
    bottomTop = bottomNav.getBoundingClientRect().top;&lt;br /&gt;
    visualGap = desiredGap * scale;&lt;br /&gt;
    targetHeight = Math.floor((bottomTop - contentRect.top - visualGap) / scale);&lt;br /&gt;
    targetHeight = Math.max(120, targetHeight);&lt;br /&gt;
&lt;br /&gt;
    currentHeight = Math.round(content.getBoundingClientRect().height / scale);&lt;br /&gt;
&lt;br /&gt;
    content.style.setProperty(&#039;--clbi-content-extra&#039;, &#039;0px&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;max-height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(currentHeight - targetHeight) &amp;gt;= 1 &amp;amp;&amp;amp; iteration &amp;lt; 4) {&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            updateClbiContentBottomGap(iteration + 1);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiContentBottomGap() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 40);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 120);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 280);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 520);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateLeftBillboardAdaptive() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: Ad는 이미지/CRT 비율을 유지하는 고정 슬롯이다.&lt;br /&gt;
    남는 세로 공간은 뉴스 박스가 흡수하므로, Ad에 하단 finish를 늘리거나&lt;br /&gt;
    title-only 상태로 접는 adaptive 보정은 사용하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftBillboardAdaptiveState();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftBillboardAdaptive() {&lt;br /&gt;
    window.requestAnimationFrame(updateLeftBillboardAdaptive);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 80);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftSidebarVerticalFit() {&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
            scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
            scheduleLeftBillboardAdaptive();&lt;br /&gt;
        }&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시대 문서 전용 왼쪽 사이드바 이미지&lt;br /&gt;
function updateLeftSidebarNationsImage() {&lt;br /&gt;
    $(&#039;#clbi-left-nations-image&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setProfileActionLabel(selector, text) {&lt;br /&gt;
    var target = $(selector);&lt;br /&gt;
    var label = target.find(&#039;.profile-action-label&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (label.length) {&lt;br /&gt;
        label.text(text);&lt;br /&gt;
    } else {&lt;br /&gt;
        target.text(text);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 사이드바 업데이트&lt;br /&gt;
function updateSidebar() {&lt;br /&gt;
    if (!window.LANG) {&lt;br /&gt;
        setTimeout(updateSidebar, 100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
&lt;br /&gt;
    var newsTitle = t.news || &#039;뉴스&#039;;&lt;br /&gt;
    var changelogTitle = t.changelog || &#039;체인지로그&#039;;&lt;br /&gt;
    var recentTitle = t.recentChanges || &#039;최근 변경&#039;;&lt;br /&gt;
    var languageTitle = t.language || &#039;언어&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-language&#039;).text(languageTitle);&lt;br /&gt;
    renderSidebarLanguageBox();&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-news&#039;).text(newsTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-recent-main&#039;).text(recentTitle);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-search a&#039;).text(t.search);&lt;br /&gt;
    $(&#039;#clbi-search-input&#039;).attr(&#039;placeholder&#039;, t.search + &#039;...&#039;);&lt;br /&gt;
    $(&#039;#clbi-title-recent a&#039;).text(recentTitle);&lt;br /&gt;
    $(&#039;#clbi-title-guide-label&#039;).text(t.guide);&lt;br /&gt;
    $(&#039;#clbi-guide-link&#039;).text(t.getStarted);&lt;br /&gt;
    $(&#039;#clbi-title-links-label&#039;).text(t.links);&lt;br /&gt;
&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-contribution&#039;, t.contribution);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-watchlist&#039;, t.watchlist);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-preferences&#039;, t.preferences);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-logout&#039;, t.logout);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-login&#039;, t.login);&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var specialPage = String(mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
$(&#039;#clbi-left-news-recent-title&#039;).text(&#039;RECENT CHANGES&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.clbi-user-btn&#039;).removeClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;Contributions&#039; ||&lt;br /&gt;
        specialPage === &#039;기여&#039; ||&lt;br /&gt;
        pageName.indexOf(&#039;특수:기여&#039;) === 0 ||&lt;br /&gt;
        pageName.indexOf(&#039;Special:Contributions&#039;) === 0&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-contribution&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (specialPage === &#039;Watchlist&#039;) {&lt;br /&gt;
        $(&#039;#clbi-btn-watchlist&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;설정&#039; ||&lt;br /&gt;
        pageName === &#039;특수:설정&#039; ||&lt;br /&gt;
        pageName === &#039;Special:설정&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-preferences&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.toggleBtn&#039;).each(function() {&lt;br /&gt;
        var btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        if (!$(&#039;#&#039; + btn.data(&#039;target&#039;)).hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            btn.text(t.expand);&lt;br /&gt;
        } else {&lt;br /&gt;
            btn.text(t.collapse);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    updateLeftSidebarNationsImage();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function canShowContentTools() {&lt;br /&gt;
    // 비로그인 사용자는 편집/역사/공유 버튼을 숨김&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // MediaWiki가 현재 문서를 편집 가능하지 않다고 판단하면 숨김&lt;br /&gt;
    var isEditable = mw.config.get(&#039;wgIsProbablyEditable&#039;);&lt;br /&gt;
    if (isEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var relevantEditable = mw.config.get(&#039;wgRelevantPageIsProbablyEditable&#039;);&lt;br /&gt;
    if (relevantEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkNodes(root) {&lt;br /&gt;
    var seen = [];&lt;br /&gt;
    var nodes = [];&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
&lt;br /&gt;
    $root.find(&#039;#catlinks, .catlinks&#039;).add($root.filter(&#039;#catlinks, .catlinks&#039;)).each(function () {&lt;br /&gt;
        if (seen.indexOf(this) !== -1) return;&lt;br /&gt;
        seen.push(this);&lt;br /&gt;
        nodes.push(this);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return nodes;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinksTarget(root) {&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
    var parserOutput = $root.find(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
    var main = $root.find(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.liberty-content-main&#039;)) {&lt;br /&gt;
        parserOutput = $(root).find(&#039;.mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.mw-parser-output&#039;)) {&lt;br /&gt;
        parserOutput = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (parserOutput.length) return parserOutput;&lt;br /&gt;
    if (main.length) return main;&lt;br /&gt;
&lt;br /&gt;
    if (!root) {&lt;br /&gt;
        parserOutput = $(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
        if (parserOutput.length) return parserOutput;&lt;br /&gt;
        if (main.length) return main;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return $();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_CATLINKS_FETCH_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function getCurrentPageTitleForCatlinks() {&lt;br /&gt;
    return String(&lt;br /&gt;
        mw.config.get(&#039;wgPageName&#039;) ||&lt;br /&gt;
        mw.config.get(&#039;wgRelevantPageName&#039;) ||&lt;br /&gt;
        &#039;&#039;&lt;br /&gt;
    ).trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function shouldFetchCatlinks() {&lt;br /&gt;
    var pageName = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) return false;&lt;br /&gt;
    if (specialPage) return false;&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearCatlinksInlineHiding(cat) {&lt;br /&gt;
    if (!cat || !cat.style) return;&lt;br /&gt;
&lt;br /&gt;
    cat.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-normal-catlinks, #mw-normal-catlinks, .mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        if (!this.style) return;&lt;br /&gt;
        this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function exposeHiddenCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        this.classList.remove(&#039;mw-hidden-cats-hidden&#039;);&lt;br /&gt;
        this.classList.add(&#039;mw-hidden-cats-user-shown&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (this.style) {&lt;br /&gt;
            this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkTextContent(cat) {&lt;br /&gt;
    var clone;&lt;br /&gt;
    var text;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    clone = cat.cloneNode(true);&lt;br /&gt;
    $(clone).find(&#039;script, style&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    text = String(clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*:\s*/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*$/i, &#039;&#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function hasRenderedCatlinkItems(cat) {&lt;br /&gt;
    var hasLink;&lt;br /&gt;
    var hasListText;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    hasLink = false;&lt;br /&gt;
    $(cat).find(&#039;a&#039;).each(function () {&lt;br /&gt;
        var text = String($(this).text() || &#039;&#039;).trim();&lt;br /&gt;
        var href = String(this.getAttribute(&#039;href&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        if (text || href) hasLink = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasLink) return true;&lt;br /&gt;
&lt;br /&gt;
    hasListText = false;&lt;br /&gt;
    $(cat).find(&#039;li&#039;).each(function () {&lt;br /&gt;
        if (String($(this).text() || &#039;&#039;).trim()) hasListText = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasListText) return true;&lt;br /&gt;
&lt;br /&gt;
    return !!getCatlinkTextContent(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCategoryTitle(rawTitle) {&lt;br /&gt;
    var title = String(rawTitle == null ? &#039;&#039; : rawTitle).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!title) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    title = title.replace(/_/g, &#039; &#039;);&lt;br /&gt;
&lt;br /&gt;
    if (/^(Category|분류):/i.test(title)) {&lt;br /&gt;
        return title;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;분류:&#039; + title;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeCategoryLinkTitle(rawTitle) {&lt;br /&gt;
    return String(rawTitle || &#039;&#039;)&lt;br /&gt;
        .replace(/^Category:/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^분류:/, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function dedupeCatlinkCategories(categories) {&lt;br /&gt;
    var seen = {};&lt;br /&gt;
    var result = [];&lt;br /&gt;
&lt;br /&gt;
    (categories || []).forEach(function (item) {&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var hidden = false;&lt;br /&gt;
&lt;br /&gt;
        if (typeof item === &#039;string&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item);&lt;br /&gt;
        } else if (item &amp;amp;&amp;amp; typeof item === &#039;object&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item.title || item.name || item.category || &#039;&#039;);&lt;br /&gt;
            hidden = item.hidden !== undefined || item.isHidden === true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
        if (seen[title]) return;&lt;br /&gt;
&lt;br /&gt;
        seen[title] = true;&lt;br /&gt;
        result.push({ title: title, hidden: hidden });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getConfigCatlinksCategories() {&lt;br /&gt;
    var normal = mw.config.get(&#039;wgCategories&#039;) || [];&lt;br /&gt;
    var hidden = mw.config.get(&#039;wgHiddenCategories&#039;) || [];&lt;br /&gt;
    var categories = [];&lt;br /&gt;
&lt;br /&gt;
    if (!Array.isArray(normal)) normal = [];&lt;br /&gt;
    if (!Array.isArray(hidden)) hidden = [];&lt;br /&gt;
&lt;br /&gt;
    normal.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: false });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    hidden.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: true });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return dedupeCatlinkCategories(categories);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksReady(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksEmpty(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksPending(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-fetching&#039;, &#039;1&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderFetchedCatlinks(cat, categories, pageTitle) {&lt;br /&gt;
    var container;&lt;br /&gt;
    var ul;&lt;br /&gt;
    var normalized;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    normalized = dedupeCatlinkCategories(categories);&lt;br /&gt;
&lt;br /&gt;
    if (!normalized.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cat.innerHTML = &#039;&#039;;&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-api-populated&#039;);&lt;br /&gt;
&lt;br /&gt;
    container = document.createElement(&#039;div&#039;);&lt;br /&gt;
    container.className = &#039;mw-normal-catlinks&#039;;&lt;br /&gt;
    container.appendChild(document.createTextNode(&#039;분류: &#039;));&lt;br /&gt;
&lt;br /&gt;
    ul = document.createElement(&#039;ul&#039;);&lt;br /&gt;
&lt;br /&gt;
    normalized.forEach(function (item) {&lt;br /&gt;
        var title = String(item &amp;amp;&amp;amp; item.title ? item.title : &#039;&#039;).trim();&lt;br /&gt;
        var li;&lt;br /&gt;
        var a;&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
&lt;br /&gt;
        li = document.createElement(&#039;li&#039;);&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = mw.util.getUrl(title);&lt;br /&gt;
        a.title = title;&lt;br /&gt;
        a.textContent = makeCategoryLinkTitle(title);&lt;br /&gt;
&lt;br /&gt;
        if (item.hidden) {&lt;br /&gt;
            li.className = &#039;clbi-hidden-category-item&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        li.appendChild(a);&lt;br /&gt;
        ul.appendChild(li);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (!ul.children.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.appendChild(ul);&lt;br /&gt;
    cat.appendChild(container);&lt;br /&gt;
    markCatlinksReady(cat, pageTitle);&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksForPage(pageTitle, callback) {&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (typeof pageTitle === &#039;function&#039;) {&lt;br /&gt;
        callback = pageTitle;&lt;br /&gt;
        pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    pageTitle = String(pageTitle || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!pageTitle || !shouldFetchCatlinks()) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;categories&#039;,&lt;br /&gt;
        titles: pageTitle,&lt;br /&gt;
        cllimit: &#039;max&#039;,&lt;br /&gt;
        clprop: &#039;hidden&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).done(function (data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages &amp;amp;&amp;amp; pages.length ? pages[0] : null;&lt;br /&gt;
        var categories = page &amp;amp;&amp;amp; page.categories ? page.categories : [];&lt;br /&gt;
&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback(categories || [], pageTitle);&lt;br /&gt;
    }).fail(function () {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finalizeEmptyCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    markCatlinksEmpty(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksIfNeeded(cat) {&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(cat, configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) {&lt;br /&gt;
        finalizeEmptyCatlinks(cat);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (cat.getAttribute(&#039;data-clbi-catlinks-fetching&#039;) === &#039;1&#039; &amp;amp;&amp;amp; cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) === pageTitle) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
    markCatlinksPending(cat, pageTitle);&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (!cat || !cat.isConnected) return;&lt;br /&gt;
        if (cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) !== requestedPage) return;&lt;br /&gt;
&lt;br /&gt;
        if (!renderFetchedCatlinks(cat, categories, requestedPage)) {&lt;br /&gt;
            finalizeEmptyCatlinks(cat);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCatlinksPanel(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksIfNeeded(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createCatlinksPanel(target, className) {&lt;br /&gt;
    var cat;&lt;br /&gt;
&lt;br /&gt;
    if (!target || !target.length) return null;&lt;br /&gt;
&lt;br /&gt;
    cat = document.createElement(&#039;div&#039;);&lt;br /&gt;
    cat.id = &#039;catlinks&#039;;&lt;br /&gt;
    cat.className = className || &#039;catlinks clbi-catlinks-created clbi-catlinks-pending&#039;;&lt;br /&gt;
    target.append(cat);&lt;br /&gt;
    return cat;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prepareSpaCatlinksBeforeInsert(root) {&lt;br /&gt;
    var nodes;&lt;br /&gt;
    var target;&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    nodes = getCatlinkNodes(root);&lt;br /&gt;
&lt;br /&gt;
    nodes.forEach(function (node) {&lt;br /&gt;
        node.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
        clearCatlinksInlineHiding(node);&lt;br /&gt;
        exposeHiddenCatlinks(node);&lt;br /&gt;
&lt;br /&gt;
        if (hasRenderedCatlinkItems(node)) {&lt;br /&gt;
            markCatlinksReady(node, pageTitle);&lt;br /&gt;
        } else if (configCategories.length) {&lt;br /&gt;
            renderFetchedCatlinks(node, configCategories, pageTitle);&lt;br /&gt;
        } else {&lt;br /&gt;
            markCatlinksEmpty(node);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (nodes.length || !configCategories.length) return;&lt;br /&gt;
&lt;br /&gt;
    target = getCatlinksTarget(root);&lt;br /&gt;
    if (!target.length) target = $(root);&lt;br /&gt;
&lt;br /&gt;
    renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveCatlinksToBottom() {&lt;br /&gt;
    var main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
    var target = getCatlinksTarget();&lt;br /&gt;
    var catlinks = getCatlinkNodes();&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!main.length || !target.length) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    catlinks.forEach(function (node) {&lt;br /&gt;
        var catNode = $(node);&lt;br /&gt;
&lt;br /&gt;
        if (node.parentNode !== target[0]) {&lt;br /&gt;
            catNode.appendTo(target);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        normalizeCatlinksPanel(node);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (catlinks.length) return;&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        var cat;&lt;br /&gt;
&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (getCatlinkNodes().length) return;&lt;br /&gt;
        if (!categories || !categories.length) return;&lt;br /&gt;
&lt;br /&gt;
        cat = createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;);&lt;br /&gt;
        renderFetchedCatlinks(cat, categories, requestedPage);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 대문 스타일&lt;br /&gt;
function initCategoryNavIfAvailable(root) {&lt;br /&gt;
    /*&lt;br /&gt;
    CategoryNav.js는 대문 카테고리 네비를 SVG로 생성한다.&lt;br /&gt;
&lt;br /&gt;
    Common.js가 SPA로 본문을 갈아끼운 뒤에는 MediaWiki 원래 페이지 로드와 달리&lt;br /&gt;
    CategoryNav.js의 초기 DOMContentLoaded만으로는 새 mount를 다시 잡지 못할 수 있다.&lt;br /&gt;
    CategoryNav.js 자체도 mw.hook(&#039;wikipage.content&#039;)를 듣지만, 로드 순서와 SPA 타이밍이&lt;br /&gt;
    엇갈릴 수 있으므로 Common.js 쪽에서도 존재 여부를 확인한 뒤 한 번 더 호출한다.&lt;br /&gt;
&lt;br /&gt;
    이 함수는 CategoryNav.js가 아직 로드되지 않았으면 아무 것도 하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    if (&lt;br /&gt;
        window.CLBI &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI.categoryNav &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI.categoryNav.init === &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        window.CLBI.categoryNav.init(root || document);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeLegacyMainPageHero() {&lt;br /&gt;
    /*&lt;br /&gt;
    기존 대문 전용 레거시 요소 정리&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    이전 대문 구조에서는 Common.js가 본문 바깥에 #clbi-main-logo를 직접 삽입하고,&lt;br /&gt;
    본문 안의 #clbi-main-crt-hero를 #clbi-main-crt-hero-wrap으로 감싸서&lt;br /&gt;
    .liberty-content-main 위쪽으로 재배치했다.&lt;br /&gt;
&lt;br /&gt;
    새 대문은 본문 내부의 .main-portal이 로고, 알림, 카테고리 네비, 이미지 피드,&lt;br /&gt;
    방명록, 상태 패널을 모두 담당한다. 따라서 Common.js가 별도 로고나 CRT 래퍼를&lt;br /&gt;
    삽입하면 새 로고/콘텐츠와 중복된다.&lt;br /&gt;
&lt;br /&gt;
    여기서는 JS가 만들던 바깥 로고와 CRT 래퍼를 제거하고, 예전 대문 원본이나&lt;br /&gt;
    캐시된 렌더 결과에 남아 있을 수 있는 #clbi-main-crt-hero도 제거한다.&lt;br /&gt;
    */&lt;br /&gt;
    $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function setNativePageTitleHiddenHard(hidden) {&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    document.querySelectorAll(selectors.join(&#039;,&#039;)).forEach(function(node) {&lt;br /&gt;
        if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
        if (hidden) {&lt;br /&gt;
            node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;visibility&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;min-height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;margin&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;padding&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;overflow&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
        } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;min-height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;margin&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;padding&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyDefaultPageTitleVisibility() {&lt;br /&gt;
    var hideTitle = true;&lt;br /&gt;
    var isSystemAssetPage = false;&lt;br /&gt;
&lt;br /&gt;
    if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isSystemAssetPage === &#039;function&#039;) {&lt;br /&gt;
        isSystemAssetPage = window.CLBI_PAGE_SHELL.isSystemAssetPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage) {&lt;br /&gt;
        hideTitle = true;&lt;br /&gt;
    } else if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isBackendOrSystemPage === &#039;function&#039;) {&lt;br /&gt;
        hideTitle = !window.CLBI_PAGE_SHELL.isBackendOrSystemPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;)&lt;br /&gt;
        .toggleClass(&#039;page-title-hidden&#039;, hideTitle)&lt;br /&gt;
        .toggleClass(&#039;page-title-visible&#039;, !hideTitle)&lt;br /&gt;
        .toggleClass(&#039;clbi-system-doc-page&#039;, isSystemAssetPage);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.renderSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.renderSystemDocIndicator();&lt;br /&gt;
    } else if (!isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.removeSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.removeSystemDocIndicator();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (hideTitle) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(false);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyMainPageStyle() {&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    if (specialPage === &#039;Preferences&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var namespaceNumber = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var isMainPage = (pageName === &#039;대문&#039;);&lt;br /&gt;
    var isUserProfilePage = (namespaceNumber === 2);&lt;br /&gt;
    var isScreenDoc = ($(&#039;.screen-header&#039;).length &amp;gt; 0);&lt;br /&gt;
    var hideTools = (isMainPage || isUserProfilePage || !canShowContentTools());&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, isUserProfilePage);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;clbi-main-page&#039;, isMainPage);&lt;br /&gt;
&lt;br /&gt;
    // 모든 문서에서 분류 바를 본문 컨테이너 아래로 이동&lt;br /&gt;
    moveCatlinksToBottom();&lt;br /&gt;
&lt;br /&gt;
    if (isMainPage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 새 대문은 .main-portal 본문 구조가 로고/히어로를 담당한다.&lt;br /&gt;
        // Common.js의 구식 바깥 로고/CRT 재배치 루틴은 사용하지 않는다.&lt;br /&gt;
        removeLegacyMainPageHero();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    } else if (isUserProfilePage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else if (isScreenDoc) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        if ($(&#039;#clbi-tools-box&#039;).length === 0 &amp;amp;&amp;amp; canShowContentTools()) {&lt;br /&gt;
            var $toolsBox = $(&#039;&amp;lt;div id=&amp;quot;clbi-tools-box&amp;quot; class=&amp;quot;clbi-left-box&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsTitle = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;관리&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsContent = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-content&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
            $toolsContent.append($(&#039;.content-tools .btn-group&#039;).clone(true));&lt;br /&gt;
            $toolsBox.append($toolsTitle).append($toolsContent);&lt;br /&gt;
            $(&#039;#clbi-left-sidebar&#039;).append($toolsBox);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!isUserProfilePage) {&lt;br /&gt;
        $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
        $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    applyDefaultPageTitleVisibility();&lt;br /&gt;
    updateSidebar();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 본문 기본 목차 제거&lt;br /&gt;
function removeNativeTocFromContent() {&lt;br /&gt;
    $(&#039;.liberty-content-main #toc, .liberty-content-main .toc&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 ID 가져오기&lt;br /&gt;
function getHeadingId(heading) {&lt;br /&gt;
    if (heading.id) {&lt;br /&gt;
        return heading.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline[id]&#039;);&lt;br /&gt;
    if (headline &amp;amp;&amp;amp; headline.id) {&lt;br /&gt;
        return headline.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 제목 텍스트 가져오기&lt;br /&gt;
function getHeadingText(heading) {&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline&#039;);&lt;br /&gt;
    var source = headline || heading;&lt;br /&gt;
    var clone = source.cloneNode(true);&lt;br /&gt;
&lt;br /&gt;
    $(clone).find(&#039;.mw-editsection, .mw-editsection-bracket, .mw-editsection-divider&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    return (clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: 긴 제목에 자동 스크롤 적용&lt;br /&gt;
function initTocTitleScroll(root) {&lt;br /&gt;
    var $items = root&lt;br /&gt;
        ? $(root).find(&#039;.toc-scroll-text&#039;)&lt;br /&gt;
        : $(&#039;#side-toc-box .toc-scroll-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    $items.each(function () {&lt;br /&gt;
        var $text = $(this);&lt;br /&gt;
        var $wrap = $text.closest(&#039;.toc-scroll-wrap&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$wrap.length) return;&lt;br /&gt;
&lt;br /&gt;
        var wrapW = Math.floor($wrap.width());&lt;br /&gt;
        var textW = Math.ceil(this.scrollWidth);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 레이아웃 계산이 끝나지 않았으면 이번 실행에서는 건드리지 않는다.&lt;br /&gt;
        if (!wrapW || !textW) return;&lt;br /&gt;
&lt;br /&gt;
        if (textW &amp;lt;= wrapW + 12) {&lt;br /&gt;
            // 왼쪽 목차: 칸을 넘지 않는 제목은 전체 텍스트를 그대로 보여준다.&lt;br /&gt;
            $wrap.removeClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
            if ($text.data(&#039;toc-scroll-enabled&#039;)) {&lt;br /&gt;
                $text.css({&lt;br /&gt;
                    animation: &#039;&#039;,&lt;br /&gt;
                    &#039;animation-delay&#039;: &#039;&#039;,&lt;br /&gt;
                    &#039;--scroll-dist&#039;: &#039;&#039;&lt;br /&gt;
                });&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-enabled&#039;);&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-key&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var scrollDist = &#039;-&#039; + (textW - wrapW + 10) + &#039;px&#039;;&lt;br /&gt;
        var duration = Math.max(7, textW / 38) * 1.25;&lt;br /&gt;
        var scrollKey = scrollDist + &#039;|&#039; + duration;&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목에는 오른쪽 페이드와 스크롤을 적용한다.&lt;br /&gt;
        $wrap.addClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 같은 값으로 이미 적용된 애니메이션은 다시 초기화하지 않는다.&lt;br /&gt;
        if ($text.data(&#039;toc-scroll-key&#039;) === scrollKey) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $text.data(&#039;toc-scroll-enabled&#039;, true);&lt;br /&gt;
        $text.data(&#039;toc-scroll-key&#039;, scrollKey);&lt;br /&gt;
&lt;br /&gt;
        $text.css({&lt;br /&gt;
            // 왼쪽 목차: 페이지 진입 직후에는 잠시 읽을 시간을 준 뒤 흐르게 한다.&lt;br /&gt;
            animation: &#039;toc-scroll-blink-reset &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
            &#039;animation-delay&#039;: &#039;1s&#039;,&lt;br /&gt;
            &#039;--scroll-dist&#039;: scrollDist&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 목차를 왼쪽 사이드바에 새로 생성&lt;br /&gt;
function moveTocToLeftSidebar() {&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: MediaWiki가 만든 원래 목차는 본문에서 제거한다.&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
&lt;br /&gt;
    var leftSidebar = document.getElementById(&#039;clbi-left-sidebar&#039;);&lt;br /&gt;
    if (!leftSidebar) return;&lt;br /&gt;
&lt;br /&gt;
    var content =&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;) ||&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!content) return;&lt;br /&gt;
&lt;br /&gt;
    var headings = Array.prototype.slice.call(&lt;br /&gt;
        content.querySelectorAll(&#039;h2, h3&#039;)&lt;br /&gt;
    ).filter(function (heading) {&lt;br /&gt;
        if (heading.closest(&#039;#toc, .toc, #side-toc-box&#039;)) return false;&lt;br /&gt;
&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
&lt;br /&gt;
        if (!id || !text) return false;&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var tocKey = headings.map(function (heading) {&lt;br /&gt;
        return getHeadingId(heading) + &#039;|&#039; + getHeadingText(heading);&lt;br /&gt;
    }).join(&#039;||&#039;);&lt;br /&gt;
&lt;br /&gt;
    var existingBox = document.getElementById(&#039;side-toc-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 같은 문서에서 같은 목차를 이미 만들었다면 다시 지우고 만들지 않는다.&lt;br /&gt;
    if (existingBox &amp;amp;&amp;amp; existingBox.getAttribute(&#039;data-toc-key&#039;) === tocKey) {&lt;br /&gt;
        initTocTitleScroll(existingBox);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (existingBox) {&lt;br /&gt;
        existingBox.remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!headings.length) return;&lt;br /&gt;
&lt;br /&gt;
    var tocBox = document.createElement(&#039;div&#039;);&lt;br /&gt;
    tocBox.className = &#039;clbi-left-box&#039;;&lt;br /&gt;
    tocBox.id = &#039;side-toc-box&#039;;&lt;br /&gt;
    tocBox.setAttribute(&#039;data-toc-key&#039;, tocKey);&lt;br /&gt;
&lt;br /&gt;
    var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
    title.className = &#039;clbi-left-title&#039;;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 박스 제목은 Lang.js의 현재 UI 언어를 따른다.&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
    var tocTitleText = (t &amp;amp;&amp;amp; t.toc) ? t.toc : &#039;목차&#039;;&lt;br /&gt;
&lt;br /&gt;
    title.textContent = tocTitleText;&lt;br /&gt;
&lt;br /&gt;
    var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
    body.className = &#039;clbi-left-content toc-sidebar-content&#039;;&lt;br /&gt;
&lt;br /&gt;
    var list = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    list.className = &#039;generated-toc&#039;;&lt;br /&gt;
&lt;br /&gt;
    headings.forEach(function (heading) {&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
        var level = heading.tagName.toLowerCase() === &#039;h3&#039; ? 3 : 2;&lt;br /&gt;
&lt;br /&gt;
        var item = document.createElement(&#039;li&#039;);&lt;br /&gt;
        item.className = &#039;toc-level-&#039; + level;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.setAttribute(&#039;href&#039;, &#039;#&#039; + id);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목 스크롤을 위해 텍스트를 별도 span으로 감싼다.&lt;br /&gt;
        var textWrap = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textWrap.className = &#039;toc-scroll-wrap&#039;;&lt;br /&gt;
&lt;br /&gt;
        var textSpan = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textSpan.className = &#039;toc-scroll-text&#039;;&lt;br /&gt;
        textSpan.textContent = text;&lt;br /&gt;
&lt;br /&gt;
        textWrap.appendChild(textSpan);&lt;br /&gt;
        link.appendChild(textWrap);&lt;br /&gt;
&lt;br /&gt;
        item.appendChild(link);&lt;br /&gt;
        list.appendChild(item);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    body.appendChild(list);&lt;br /&gt;
    tocBox.appendChild(title);&lt;br /&gt;
    tocBox.appendChild(body);&lt;br /&gt;
    leftSidebar.appendChild(tocBox);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: DOM 배치가 끝난 뒤 긴 제목 스크롤 여부를 계산한다.&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        initTocTitleScroll(tocBox);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            initTocTitleScroll(tocBox);&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 우측 광고판: 이미지/번역 캡션 목록&lt;br /&gt;
var RIGHT_BILLBOARD_ITEMS = [&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
        alt: &#039;PROOF TO THE WORLD / YOU ONCE PART OF IT&#039;,&lt;br /&gt;
        duration: 3000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;당신이 한때 이 세계의&#039;,&lt;br /&gt;
            &#039;&#039;,&lt;br /&gt;
            &#039;일부였다는 것을 증명하십시오&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-003.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    }&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItem(index) {&lt;br /&gt;
    var items = RIGHT_BILLBOARD_ITEMS;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        return {&lt;br /&gt;
            file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
            alt: &#039;&#039;,&lt;br /&gt;
            caption: []&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var normalized = index % items.length;&lt;br /&gt;
    if (normalized &amp;lt; 0) normalized += items.length;&lt;br /&gt;
&lt;br /&gt;
    return items[normalized];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardImageUrl(fileName) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/&#039; + encodeURIComponent(fileName || &#039;Side-visual-001.png&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardCaptionHtml(item) {&lt;br /&gt;
    var lines = item &amp;amp;&amp;amp; item.caption ? item.caption : [];&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    function escapeCaptionText(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/\&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    lines.forEach(function(line) {&lt;br /&gt;
        var text = String(line == null ? &#039;&#039; : line);&lt;br /&gt;
        var isGap = !text.trim();&lt;br /&gt;
        var className = &#039;right-billboard-caption-line&#039; + (isGap ? &#039; is-gap&#039; : &#039;&#039;);&lt;br /&gt;
        html += &#039;&amp;lt;span class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; + (isGap ? &#039;&amp;amp;nbsp;&#039; : escapeCaptionText(text)) + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setRightBillboardItem(index) {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    var item = getRightBillboardItem(index);&lt;br /&gt;
    var src = getRightBillboardImageUrl(item.file);&lt;br /&gt;
    var images = box.querySelectorAll(&#039;.right-billboard-image&#039;);&lt;br /&gt;
    var caption = box.querySelector(&#039;#right-billboard-caption&#039;);&lt;br /&gt;
    var emptySub = box.querySelector(&#039;.right-billboard-empty-sub&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, String(index));&lt;br /&gt;
    box.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(images, function(img) {&lt;br /&gt;
        img.style.display = &#039;&#039;;&lt;br /&gt;
        img.setAttribute(&#039;src&#039;, src);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, img.classList.contains(&#039;right-billboard-image-base&#039;) ? (item.alt || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (caption) {&lt;br /&gt;
        caption.innerHTML = getRightBillboardCaptionHtml(item);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (emptySub) {&lt;br /&gt;
        emptySub.textContent = item.file || &#039;Side-visual-001.png&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItemDuration(item) {&lt;br /&gt;
    var duration = item &amp;amp;&amp;amp; item.duration ? parseInt(item.duration, 10) : 3000;&lt;br /&gt;
&lt;br /&gt;
    if (Number.isNaN(duration) || duration &amp;lt; 500) {&lt;br /&gt;
        duration = 3000;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return duration;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initRightBillboardCarousel() {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-billboard-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    setRightBillboardItem(0);&lt;br /&gt;
&lt;br /&gt;
    if (!RIGHT_BILLBOARD_ITEMS || RIGHT_BILLBOARD_ITEMS.length &amp;lt;= 1) return;&lt;br /&gt;
&lt;br /&gt;
    function scheduleNext() {&lt;br /&gt;
        var current = parseInt(box.getAttribute(&#039;data-billboard-index&#039;) || &#039;0&#039;, 10);&lt;br /&gt;
        if (Number.isNaN(current)) current = 0;&lt;br /&gt;
&lt;br /&gt;
        var currentItem = getRightBillboardItem(current);&lt;br /&gt;
        var delay = getRightBillboardItemDuration(currentItem);&lt;br /&gt;
&lt;br /&gt;
        window.setTimeout(function() {&lt;br /&gt;
            if (!document.body.contains(box)) return;&lt;br /&gt;
&lt;br /&gt;
            if (!document.hidden) {&lt;br /&gt;
                setRightBillboardItem(current + 1);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            scheduleNext();&lt;br /&gt;
        }, delay);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scheduleNext();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function escapeRightBillboardAttr(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildRightBillboardBox() {&lt;br /&gt;
    var billboardInitial = getRightBillboardItem(0);&lt;br /&gt;
    var billboardSrc = getRightBillboardImageUrl(billboardInitial.file);&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box right-billboard-box left-billboard-box left-ad-box&amp;quot; data-billboard-index=&amp;quot;0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title right-billboard-title left-ad-title left-ad-title-iconless&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-ad&amp;quot; class=&amp;quot;left-ad-title-label&amp;quot;&amp;gt;Looking for a job?&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content left-ad-content-shell&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;right-billboard-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-recess&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;right-billboard-screen&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;right-billboard-image&amp;quot; class=&amp;quot;right-billboard-image right-billboard-image-base&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&#039; + escapeRightBillboardAttr(billboardInitial.alt || &#039;&#039;) + &#039;&amp;quot; onload=&amp;quot;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.remove(\&#039;is-empty\&#039;);}&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.add(\&#039;is-empty\&#039;);}&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-image-bloom&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-a&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-b&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-c&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-glitch&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-tear&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-empty&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-main&amp;quot;&amp;gt;SIGNAL EMPTY&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-sub&amp;quot;&amp;gt;&#039; + escapeRightBillboardAttr(billboardInitial.file || &#039;Side-visual-001.png&#039;) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;right-billboard-caption&amp;quot; class=&amp;quot;right-billboard-caption&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + getRightBillboardCaptionHtml(billboardInitial) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-bottom-finish left-ad-bottom-finish&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var GREAT_WALL_DATA_TITLE = &#039;프로젝트:The_Great_Wall/Data.json&#039;;&lt;br /&gt;
var GREAT_WALL_LIST_LIMIT = 0;&lt;br /&gt;
var greatWallState = {&lt;br /&gt;
    data: { entries: {} },&lt;br /&gt;
    loaded: false,&lt;br /&gt;
    loading: false,&lt;br /&gt;
    saving: false,&lt;br /&gt;
    selectedOwnEntry: false,&lt;br /&gt;
    statusText: &#039;&#039;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function normalizeGreatWallData(data) {&lt;br /&gt;
    var normalized = { entries: {} };&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    Object.keys(entries).forEach(function(key) {&lt;br /&gt;
        var item = entries[key];&lt;br /&gt;
        var user = item &amp;amp;&amp;amp; item.user ? String(item.user) : String(key || &#039;&#039;);&lt;br /&gt;
        var text = item &amp;amp;&amp;amp; item.text ? String(item.text) : &#039;&#039;;&lt;br /&gt;
        var timestamp = item &amp;amp;&amp;amp; item.timestamp ? String(item.timestamp) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!user || !text) return;&lt;br /&gt;
&lt;br /&gt;
        normalized.entries[user] = {&lt;br /&gt;
            user: user,&lt;br /&gt;
            text: text.slice(0, 140),&lt;br /&gt;
            timestamp: timestamp || new Date(0).toISOString()&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseGreatWallData(text) {&lt;br /&gt;
    var parsed;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;The Great Wall data parse failed:&#039;, err);&lt;br /&gt;
        parsed = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return normalizeGreatWallData(parsed);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function stringifyGreatWallData(data) {&lt;br /&gt;
    return JSON.stringify(normalizeGreatWallData(data), null, 2) + &#039;\n&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallRevisionText(page) {&lt;br /&gt;
    var rev;&lt;br /&gt;
    var slot;&lt;br /&gt;
&lt;br /&gt;
    if (!page || !page.revisions || !page.revisions.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    rev = page.revisions[0];&lt;br /&gt;
&lt;br /&gt;
    if (rev.slots &amp;amp;&amp;amp; rev.slots.main) {&lt;br /&gt;
        slot = rev.slots.main;&lt;br /&gt;
        return slot.content || slot[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return rev.content || rev[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchGreatWallData() {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
    return api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;revisions&#039;,&lt;br /&gt;
        titles: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
        rvprop: &#039;content|timestamp&#039;,&lt;br /&gt;
        rvslots: &#039;main&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages[0] || null;&lt;br /&gt;
&lt;br /&gt;
        if (!page || page.missing) {&lt;br /&gt;
            return { entries: {} };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parseGreatWallData(getGreatWallRevisionText(page));&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall load failed:&#039;, err);&lt;br /&gt;
        return { entries: {} };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallEntries(data) {&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    return Object.keys(entries).map(function(key) {&lt;br /&gt;
        return entries[key];&lt;br /&gt;
    }).filter(function(item) {&lt;br /&gt;
        return item &amp;amp;&amp;amp; item.user &amp;amp;&amp;amp; item.text;&lt;br /&gt;
    }).sort(function(a, b) {&lt;br /&gt;
        return String(a.timestamp || &#039;&#039;).localeCompare(String(b.timestamp || &#039;&#039;));&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallMessageTime(timestamp) {&lt;br /&gt;
    var date = timestamp ? new Date(timestamp) : null;&lt;br /&gt;
    var month;&lt;br /&gt;
    var day;&lt;br /&gt;
    var hour;&lt;br /&gt;
    var minute;&lt;br /&gt;
&lt;br /&gt;
    if (!date || isNaN(date.getTime())) return &#039;—&#039;;&lt;br /&gt;
&lt;br /&gt;
    month = String(date.getMonth() + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
    day = String(date.getDate()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    hour = String(date.getHours()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    minute = String(date.getMinutes()).padStart(2, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    return month + &#039;.&#039; + day + &#039; &#039; + hour + &#039;:&#039; + minute;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallAvatarSrc(user) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(String(user || &#039;&#039;)) + &#039;.png&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallEntryHtml(item, currentUser, selectedOwnEntry) {&lt;br /&gt;
    var isOwn = currentUser &amp;amp;&amp;amp; item.user === currentUser;&lt;br /&gt;
    var tag = isOwn ? &#039;button&#039; : &#039;div&#039;;&lt;br /&gt;
    var attrs = isOwn ? &#039; type=&amp;quot;button&amp;quot; data-great-wall-own-entry=&amp;quot;1&amp;quot; aria-label=&amp;quot;Edit your wall message&amp;quot;&#039; : &#039;&#039;;&lt;br /&gt;
    var className = &#039;great-wall-entry&#039; + (isOwn ? &#039; is-own&#039; : &#039;&#039;) + (isOwn &amp;amp;&amp;amp; selectedOwnEntry ? &#039; is-selected&#039; : &#039;&#039;);&lt;br /&gt;
    var avatarSrc = getGreatWallAvatarSrc(item.user);&lt;br /&gt;
    var messageTime = getGreatWallMessageTime(item.timestamp);&lt;br /&gt;
    var isoTime = item.timestamp || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;&#039; + tag + attrs + &#039; class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;img class=&amp;quot;great-wall-avatar&amp;quot; src=&amp;quot;&#039; + escapeClbiHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=&amp;amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;quot;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-bubble&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-entry-head&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-user&amp;quot;&amp;gt;@&#039; + escapeClbiHtml(item.user) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-time&amp;quot; title=&amp;quot;&#039; + escapeClbiHtml(isoTime) + &#039;&amp;quot;&amp;gt;&#039; + escapeClbiHtml(messageTime) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-text&amp;quot;&amp;gt;&#039; + escapeClbiHtml(item.text) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/&#039; + tag + &#039;&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var list = document.getElementById(&#039;great-wall-list&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
    var status = document.getElementById(&#039;great-wall-status&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var entries = getGreatWallEntries(greatWallState.data);&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var visibleEntries = [];&lt;br /&gt;
    var seen = {};&lt;br /&gt;
&lt;br /&gt;
    if (!box || !list) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.toggle(&#039;is-guest&#039;, !currentUser);&lt;br /&gt;
    box.classList.toggle(&#039;is-loading&#039;, !!greatWallState.loading);&lt;br /&gt;
    box.classList.toggle(&#039;is-saving&#039;, !!greatWallState.saving);&lt;br /&gt;
    box.classList.toggle(&#039;has-own-entry&#039;, !!ownEntry);&lt;br /&gt;
    box.classList.toggle(&#039;is-own-selected&#039;, !!greatWallState.selectedOwnEntry);&lt;br /&gt;
&lt;br /&gt;
    entries.forEach(function(item) {&lt;br /&gt;
        if (seen[item.user]) return;&lt;br /&gt;
        visibleEntries.push(item);&lt;br /&gt;
        seen[item.user] = true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.loading &amp;amp;&amp;amp; !greatWallState.loaded) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else if (!visibleEntries.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;NO MARKS&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else {&lt;br /&gt;
        list.innerHTML = visibleEntries.map(function(item) {&lt;br /&gt;
            return buildGreatWallEntryHtml(item, currentUser, greatWallState.selectedOwnEntry);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list &amp;amp;&amp;amp; list.scrollHeight &amp;gt; list.clientHeight) {&lt;br /&gt;
        list.scrollTop = list.scrollHeight;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (status) {&lt;br /&gt;
        // Status text is intentionally suppressed in the composer strip; the area is spacing-only UI.&lt;br /&gt;
        status.textContent = &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!input || !submit) return;&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        submit.disabled = false;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = &#039;Login required&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.saving || greatWallState.loading) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = greatWallState.saving ? &#039;SAVE&#039; : &#039;SYNC&#039;;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = greatWallState.saving ? &#039;Saving&#039; : &#039;Syncing&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.disabled = !ownEntry;&lt;br /&gt;
        deleteButton.title = ownEntry ? &#039;Delete your mark&#039; : &#039;No mark to delete&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        input.disabled = false;&lt;br /&gt;
        input.readOnly = true;&lt;br /&gt;
        input.setAttribute(&#039;aria-readonly&#039;, &#039;true&#039;);&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = &#039;UPDATE&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
    input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; greatWallState.selectedOwnEntry &amp;amp;&amp;amp; !input.value) {&lt;br /&gt;
        input.value = ownEntry.text || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    submit.disabled = false;&lt;br /&gt;
    submit.textContent = ownEntry ? &#039;UPDATE&#039; : &#039;POST&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function saveGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var text = input ? String(input.value || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!text) {&lt;br /&gt;
        greatWallState.statusText = &#039;EMPTY MARK&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (text.length &amp;gt; 140) {&lt;br /&gt;
        text = text.slice(0, 140);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SAVING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        data.entries[currentUser] = {&lt;br /&gt;
            user: currentUser,&lt;br /&gt;
            text: text,&lt;br /&gt;
            timestamp: new Date().toISOString()&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Update The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK UPDATED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall save failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;SAVE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function deleteGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!ownEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;NO MARK TO DELETE&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;DELETING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        if (data.entries &amp;amp;&amp;amp; data.entries[currentUser]) {&lt;br /&gt;
            delete data.entries[currentUser];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Delete The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK DELETED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall delete failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;DELETE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBoxWhenReady() {&lt;br /&gt;
    if (!document.getElementById(&#039;great-wall-sidebar&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    if (mw.Api) {&lt;br /&gt;
        initGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.loader &amp;amp;&amp;amp; mw.loader.using) {&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
            initGreatWallBox();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-great-wall-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-great-wall-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var ownButton = e.target.closest ? e.target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;]&#039;) : null;&lt;br /&gt;
        var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
        var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
&lt;br /&gt;
        if (!ownButton || !ownEntry) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = true;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
        if (input) {&lt;br /&gt;
            input.focus();&lt;br /&gt;
            input.setSelectionRange(input.value.length, input.value.length);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var target = e.target;&lt;br /&gt;
        var keepSelection;&lt;br /&gt;
&lt;br /&gt;
        if (!greatWallState.selectedOwnEntry || !target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
        keepSelection = target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;], .great-wall-editor, .great-wall-compose-sector&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (keepSelection) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        if (input) input.value = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (submit) {&lt;br /&gt;
        submit.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
                window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            saveGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            deleteGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (input) {&lt;br /&gt;
        input.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
            if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
                e.preventDefault();&lt;br /&gt;
                saveGreatWallEntry();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&#039;input&#039;, function() {&lt;br /&gt;
            if (input.value.length &amp;gt; 140) {&lt;br /&gt;
                input.value = input.value.slice(0, 140);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.loading = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SYNCING WALL&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        greatWallState.data = normalizeGreatWallData(data);&lt;br /&gt;
        greatWallState.loaded = true;&lt;br /&gt;
        greatWallState.loading = false;&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;great-wall-sidebar&amp;quot; class=&amp;quot;clbi-right-box great-wall-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title great-wall-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-great-wall&amp;quot;&amp;gt;The Great Wall&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content great-wall-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-list&amp;quot; class=&amp;quot;great-wall-list&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-compose-sector&amp;quot; aria-label=&amp;quot;The Great Wall editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;input id=&amp;quot;great-wall-input&amp;quot; class=&amp;quot;great-wall-input&amp;quot; type=&amp;quot;text&amp;quot; maxlength=&amp;quot;140&amp;quot; autocomplete=&amp;quot;off&amp;quot; placeholder=&amp;quot;담벼락&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-submit&amp;quot; class=&amp;quot;great-wall-submit&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;POST&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-delete&amp;quot; class=&amp;quot;great-wall-delete&amp;quot; type=&amp;quot;button&amp;quot; disabled&amp;gt;DEL&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-status&amp;quot; class=&amp;quot;great-wall-status&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeMainPortalGuestbookPreview() {&lt;br /&gt;
    var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    var guestbook = portal ? portal.querySelector(&#039;.guestbook-device&#039;) : null;&lt;br /&gt;
    var sideScreen;&lt;br /&gt;
    var grid;&lt;br /&gt;
&lt;br /&gt;
    if (!portal || !guestbook) return;&lt;br /&gt;
&lt;br /&gt;
    sideScreen = guestbook.closest ? guestbook.closest(&#039;.side-screen&#039;) : null;&lt;br /&gt;
    grid = sideScreen &amp;amp;&amp;amp; sideScreen.closest ? sideScreen.closest(&#039;.console-grid&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (guestbook.parentNode) {&lt;br /&gt;
        guestbook.parentNode.removeChild(guestbook);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sideScreen &amp;amp;&amp;amp; !(sideScreen.textContent || &#039;&#039;).trim() &amp;amp;&amp;amp; !sideScreen.querySelector(&#039;img,svg,video,canvas,form,input,button,a&#039;)) {&lt;br /&gt;
        if (sideScreen.parentNode) {&lt;br /&gt;
            sideScreen.parentNode.removeChild(sideScreen);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (grid) {&lt;br /&gt;
            grid.style.gridTemplateColumns = &#039;minmax(0,1fr)&#039;;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    portal.classList.add(&#039;is-great-wall-relocated&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildSiteInformationBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-right-box site-info-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title site-info-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span&amp;gt;정보&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content site-info-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;policy-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/개인정보처리방침&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;개인정보처리방침&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/면책_조항&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;면책 조항&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/라이선스&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;라이선스&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/크레딧&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;크레딧&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;social-strip&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://discord.gg/ctaeJ9d3Q5&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;DC&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.youtube.com/@nxdsxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;YT&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://x.com/nxd_sxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;X&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;WIP:&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 초기화 함수&lt;br /&gt;
function initSidebars() {&lt;br /&gt;
    var header = $(&#039;.liberty-content-header&#039;);&lt;br /&gt;
    var content = $(&#039;.liberty-content&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (header.length &amp;amp;&amp;amp; content.length) {&lt;br /&gt;
        header.prependTo(content);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-right-sidebar&#039;).length === 0) {&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        var isLoggedIn = username !== null;&lt;br /&gt;
        var avatarSrc = isLoggedIn&lt;br /&gt;
            ? &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + username + &#039;.png&#039;&lt;br /&gt;
            : &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&#039;;&lt;br /&gt;
&lt;br /&gt;
        var userBox;&lt;br /&gt;
&lt;br /&gt;
        if (isLoggedIn) {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a href=&amp;quot;/index.php/사용자:&#039; + username + &#039;&amp;quot; id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;&#039; + username + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-quick-actions&amp;quot; aria-label=&amp;quot;프로필 빠른 메뉴&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-inventory&amp;quot; aria-label=&amp;quot;인벤토리&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_PACKAGE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;인벤토리&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-achievements&amp;quot; aria-label=&amp;quot;업적&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_TROPHY + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;업적&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-notifications&amp;quot; aria-label=&amp;quot;알림&amp;quot;&amp;gt;&amp;lt;span id=&amp;quot;profile-quick-notification-icon&amp;quot; class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_BELL + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;알림&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:기여/&#039; + username + &#039;&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-contribution&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_TEXT + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;기여&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:주시문서목록&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-watchlist&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_EYE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;주시문서 목록&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:설정&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-preferences&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SETTINGS + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;설정&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그아웃&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn clbi-user-btn-logout&amp;quot; id=&amp;quot;clbi-btn-logout&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그아웃&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row profile-name-row-guest&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;Guest&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그인&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-login&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그인&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        var greatWallBox = &#039;&#039;;&lt;br /&gt;
        var siteInformationBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            greatWallBox = buildGreatWallBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;The Great Wall build failed:&#039;, err);&lt;br /&gt;
            greatWallBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            siteInformationBox = buildSiteInformationBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Site information build failed:&#039;, err);&lt;br /&gt;
            siteInformationBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        var sidebar = userBox + greatWallBox + siteInformationBox;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).append(&#039;&amp;lt;div id=&amp;quot;clbi-right-sidebar&amp;quot;&amp;gt;&#039; + sidebar + &#039;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        removeMainPortalGuestbookPreview();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    initGreatWallBoxWhenReady();&lt;br /&gt;
    removeMainPortalGuestbookPreview();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-left-sidebar&#039;).length === 0) {&lt;br /&gt;
var leftBillboardBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            leftBillboardBox = buildRightBillboardBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Left billboard build failed:&#039;, err);&lt;br /&gt;
            leftBillboardBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
var leftSidebar =&lt;br /&gt;
    &#039;&amp;lt;div id=&amp;quot;clbi-left-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-language&amp;quot;&amp;gt;언어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content sidebar-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selector&amp;quot; class=&amp;quot;sidebar-lang-selector sidebar-lang-dial&amp;quot; tabindex=&amp;quot;0&amp;quot; role=&amp;quot;group&amp;quot; aria-label=&amp;quot;언어 선택&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-dial-stage&amp;quot; class=&amp;quot;sidebar-lang-dial-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-fan&amp;quot; class=&amp;quot;sidebar-lang-fan&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selected-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-left&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-selected-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;한국어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-availability-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-right is-current&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-availability-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;CURRENT&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-sidebar-lang-apply&amp;quot; class=&amp;quot;sidebar-lang-apply&amp;quot; aria-label=&amp;quot;언어 적용&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;sidebar-lang-apply-mark&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;✓&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-news&amp;quot;&amp;gt;뉴스&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content clbi-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-title&amp;quot;&amp;gt;CHANGELOG&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-changelog-feed&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;a href=&amp;quot;/index.php/체인지로그&amp;quot; class=&amp;quot;news-post-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-post-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-post-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-main&amp;quot;&amp;gt;체인지로그&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-post-jump&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-divider&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-recent-title&amp;quot;&amp;gt;RECENT CHANGES&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-recent-feed&amp;quot; id=&amp;quot;clbi-left-recent-list&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;a class=&amp;quot;news-fill-image-slot&amp;quot; id=&amp;quot;clbi-left-news-fill-image&amp;quot; href=&amp;quot;/index.php/특수:최근바뀜&amp;quot; aria-label=&amp;quot;최근 바뀜으로 이동&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-fill-image-frame&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-fill-image&amp;quot; style=&amp;quot;--news-fill-image-url:url(\&#039;/index.php?title=특수:Redirect/file/Side-news-fill-001.png\&#039;);&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;img class=&amp;quot;news-fill-image-probe&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Side-news-fill-001.png&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.closest(\&#039;.news-fill-image-slot\&#039;).classList.add(\&#039;is-empty\&#039;);this.remove();&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        leftBillboardBox +&lt;br /&gt;
    &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).prepend(leftSidebar);&lt;br /&gt;
&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
        loadRecentChangesList(&#039;#clbi-left-recent-list&#039;, 10);&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
        updateLeftSidebarNationsImage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        initRightBillboardCarousel();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;Right billboard carousel failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
    applyMainPageStyle();&lt;br /&gt;
    initClbiCustomDocumentScrollbars();&lt;br /&gt;
    initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
        window.ProgressSystemWebUi.boot(&#039;initSidebars&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initNotifications();&lt;br /&gt;
            initProfile();&lt;br /&gt;
            moveTocToLeftSidebar();&lt;br /&gt;
        }, 300);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 800);&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 1500);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    loadLangScript(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initSidebars();&lt;br /&gt;
        }, 100);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click.profileQuickPlaceholder&#039;, &#039;#profile-quick-inventory, #profile-quick-achievements&#039;, function(e) {&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
function extractJsonArrayAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var depth = 0;&lt;br /&gt;
    var inString = false;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = String(text || &#039;&#039;).indexOf(&#039;[&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (inString) {&lt;br /&gt;
            if (escaped) {&lt;br /&gt;
                escaped = false;&lt;br /&gt;
            } else if (ch === &#039;\\&#039;) {&lt;br /&gt;
                escaped = true;&lt;br /&gt;
            } else if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
                inString = false;&lt;br /&gt;
            }&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            inString = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;[&#039;) depth += 1;&lt;br /&gt;
        if (ch === &#039;]&#039;) {&lt;br /&gt;
            depth -= 1;&lt;br /&gt;
            if (depth === 0) {&lt;br /&gt;
                try {&lt;br /&gt;
                    return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    return null;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function extractJsonStringAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var colon;&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    colon = text.indexOf(&#039;:&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (colon === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = text.indexOf(&#039;&amp;quot;&#039;, colon + 1);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start + 1; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (escaped) {&lt;br /&gt;
            escaped = false;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;\\&#039;) {&lt;br /&gt;
            escaped = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                return text.slice(start + 1, i);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function syncCatlinksConfigFromSpaDocument(doc) {&lt;br /&gt;
    var scripts = doc ? doc.querySelectorAll(&#039;script&#039;) : [];&lt;br /&gt;
    var categories = null;&lt;br /&gt;
    var hiddenCategories = null;&lt;br /&gt;
    var relevantPageName = null;&lt;br /&gt;
    var pageName = null;&lt;br /&gt;
    var i;&lt;br /&gt;
    var text;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; scripts.length; i += 1) {&lt;br /&gt;
        text = scripts[i].textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (categories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) categories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (hiddenCategories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgHiddenCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) hiddenCategories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (relevantPageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgRelevantPageName&#039;);&lt;br /&gt;
            if (value !== null) relevantPageName = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgPageName&#039;);&lt;br /&gt;
            if (value !== null) pageName = value;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.config.set(&#039;wgCategories&#039;, Array.isArray(categories) ? categories : []);&lt;br /&gt;
    mw.config.set(&#039;wgHiddenCategories&#039;, Array.isArray(hiddenCategories) ? hiddenCategories : []);&lt;br /&gt;
&lt;br /&gt;
    if (relevantPageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, relevantPageName);&lt;br /&gt;
    } else if (pageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    CLBI_CATLINKS_FETCH_TOKEN += 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// SPA 네비게이션&lt;br /&gt;
function shouldSkip(url) {&lt;br /&gt;
    return url.match(/action=edit|action=submit|action=history|action=delete|action=protect|action=purge|특수:로그인|특수:로그아웃|Special:UserLogin|Special:UserLogout|특수:사용자정보|특수:비밀번호바꾸기|uselang=/);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    if (window._spaInitialized) return;&lt;br /&gt;
    window._spaInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    function isInternal(url) {&lt;br /&gt;
        var a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = url;&lt;br /&gt;
        return a.hostname === window.location.hostname;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getCachedSpaPageHtml(url) {&lt;br /&gt;
        if (!window.EntryStore || typeof window.EntryStore.getTextSync !== &#039;function&#039;) return &#039;&#039;;&lt;br /&gt;
        return window.EntryStore.getTextSync(url) || window.EntryStore.getTextSync(String(url || &#039;&#039;).replace(/^https?:\/\/[^/]+/i, &#039;&#039;)) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchSpaPageHtml(url) {&lt;br /&gt;
        var cached = getCachedSpaPageHtml(url);&lt;br /&gt;
        if (cached) return Promise.resolve(cached);&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; }).then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDetachedEntryContent(newContent) {&lt;br /&gt;
        /*&lt;br /&gt;
        Initial boot prepares entry artifacts; SPA is only allowed to consume them.&lt;br /&gt;
        This hook runs while the fetched page is still detached, before the user sees it.&lt;br /&gt;
        It must stay synchronous or already-resolved: if a subsystem cannot prepare from&lt;br /&gt;
        EntryStore immediately, it should leave the old fallback path in place instead of&lt;br /&gt;
        opening BootGate during SPA.&lt;br /&gt;
        */&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.prepareContentForEntry === &#039;function&#039;) {&lt;br /&gt;
                window.NationsPanel.prepareContentForEntry(newContent);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;entry content preparation failed:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadPage(url) {&lt;br /&gt;
        invalidateProfileRender();&lt;br /&gt;
&lt;br /&gt;
        return fetchSpaPageHtml(url)&lt;br /&gt;
            .then(function(html) {&lt;br /&gt;
                var parser = new DOMParser();&lt;br /&gt;
                var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
                var scripts = doc.querySelectorAll(&#039;script&#039;);&lt;br /&gt;
                for (var i = 0; i &amp;lt; scripts.length; i++) {&lt;br /&gt;
                    var src = scripts[i].textContent;&lt;br /&gt;
&lt;br /&gt;
                    if (src.indexOf(&#039;wgNamespaceNumber&#039;) !== -1) {&lt;br /&gt;
                        var match = src.match(/&amp;quot;wgNamespaceNumber&amp;quot;:(-?\d+)/);&lt;br /&gt;
                        if (match) mw.config.set(&#039;wgNamespaceNumber&#039;, parseInt(match[1], 10));&lt;br /&gt;
&lt;br /&gt;
                        var matchTitle = src.match(/&amp;quot;wgTitle&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchTitle) mw.config.set(&#039;wgTitle&#039;, matchTitle[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchPage = src.match(/&amp;quot;wgPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchPage) mw.config.set(&#039;wgPageName&#039;, matchPage[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchArticle = src.match(/&amp;quot;wgArticleId&amp;quot;:(\d+)/);&lt;br /&gt;
                        if (matchArticle) {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, parseInt(matchArticle[1], 10));&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, 0);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchIsMainPage = src.match(/&amp;quot;wgIsMainPage&amp;quot;:(true|false)/);&lt;br /&gt;
                        if (matchIsMainPage) {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, matchIsMainPage[1] === &#039;true&#039;);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchSpecial = src.match(/&amp;quot;wgCanonicalSpecialPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchSpecial) {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, matchSpecial[1]);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                syncCatlinksConfigFromSpaDocument(doc);&lt;br /&gt;
&lt;br /&gt;
                var newContent = doc.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
                var newTitle = doc.querySelector(&#039;.mw-page-title-main&#039;);&lt;br /&gt;
                var newHead = doc.querySelector(&#039;title&#039;);&lt;br /&gt;
                var newHeader = doc.querySelector(&#039;.liberty-content-header&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (newContent) {&lt;br /&gt;
                    prepareDetachedEntryContent(newContent);&lt;br /&gt;
                    prepareSpaCatlinksBeforeInsert(newContent);&lt;br /&gt;
                    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
                    $(&#039;.liberty-content-main&#039;).html(newContent.innerHTML);&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    try {&lt;br /&gt;
                        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.renderPrepared === &#039;function&#039;) window.Decorations.renderPrepared();&lt;br /&gt;
                        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.renderPrepared === &#039;function&#039;) window.CLBI_DECORATIONS.renderPrepared();&lt;br /&gt;
                    } catch (err) {}&lt;br /&gt;
                    $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newTitle) {&lt;br /&gt;
                    $(&#039;.mw-page-title-main&#039;).html(newTitle.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHead) {&lt;br /&gt;
                    document.title = newHead.textContent;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHeader) {&lt;br /&gt;
                    $(&#039;.liberty-content-header&#039;).html(newHeader.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
                window.scrollTo(0, 0);&lt;br /&gt;
                mw.hook(&#039;wikipage.content&#039;).fire($(&#039;.liberty-content-main&#039;));&lt;br /&gt;
                applyMainPageStyle();&lt;br /&gt;
                initClbiCustomDocumentScrollbars();&lt;br /&gt;
                initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
                if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.handleSpaPageView === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.handleSpaPageView();&lt;br /&gt;
                } else if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.boot(&#039;spa&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 100);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 500);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 1200);&lt;br /&gt;
&lt;br /&gt;
                mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
                    initProfile();&lt;br /&gt;
                    moveTocToLeftSidebar();&lt;br /&gt;
                });&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function (err) {&lt;br /&gt;
                console.error(&#039;SPA page load failed:&#039;, err);&lt;br /&gt;
                $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
// 목차 링크는 전용 처리&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;#side-toc-box a, #toc a, .toc a&#039;, function(e) {&lt;br /&gt;
    var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
    if (!href || href.charAt(0) !== &#039;#&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var rawId = href.slice(1);&lt;br /&gt;
    if (!rawId) return;&lt;br /&gt;
&lt;br /&gt;
    var decodedId = rawId;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        decodedId = decodeURIComponent(rawId);&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        decodedId = rawId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var target = document.getElementById(decodedId);&lt;br /&gt;
&lt;br /&gt;
    if (!target &amp;amp;&amp;amp; window.CSS &amp;amp;&amp;amp; CSS.escape) {&lt;br /&gt;
        target = document.querySelector(&#039;#&#039; + CSS.escape(decodedId));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!target) return;&lt;br /&gt;
&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
    var scrollTarget = target.closest(&#039;h2, h3&#039;) || target;&lt;br /&gt;
&lt;br /&gt;
    scrollTarget.scrollIntoView({&lt;br /&gt;
        behavior: &#039;auto&#039;,&lt;br /&gt;
        block: &#039;start&#039;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    history.replaceState(null, &#039;&#039;, &#039;#&#039; + rawId);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
    /* 길라잡이는 문서 링크가 아니라 대문 본문 화면 탭이다. */&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;, function(e) {&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.toggle === &#039;function&#039;) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopImmediatePropagation();&lt;br /&gt;
            window.BottomGuideNav.toggle();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;a&#039;, function(e) {&lt;br /&gt;
        // 휠 클릭, 새 탭 열기, 보조키 이동은 브라우저 기본 동작을 유지한다.&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        /* 길라잡이 화면 탭은 위 전용 처리 외에는 SPA 문서 이동 대상으로 삼지 않는다. */&lt;br /&gt;
        if ($(this).is(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
        if (!href) return;&lt;br /&gt;
&lt;br /&gt;
        // 목차 링크는 별도 핸들러에서 처리&lt;br /&gt;
        if ($(this).closest(&#039;#side-toc-box, #toc, .toc&#039;).length) return;&lt;br /&gt;
&lt;br /&gt;
        // 단순 해시 링크는 SPA 가로채기 제외&lt;br /&gt;
        if (href.startsWith(&#039;#&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.href = href;&lt;br /&gt;
&lt;br /&gt;
        var samePath = decodeURIComponent(link.pathname) === decodeURIComponent(window.location.pathname);&lt;br /&gt;
        var sameSearch = (link.search || &#039;&#039;) === (window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; samePath &amp;amp;&amp;amp; sameSearch) return;&lt;br /&gt;
&lt;br /&gt;
        var currentBase = window.location.href.split(&#039;#&#039;)[0];&lt;br /&gt;
        var targetBase = link.href.split(&#039;#&#039;)[0];&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; currentBase === targetBase) return;&lt;br /&gt;
&lt;br /&gt;
        if (!isInternal(href)) return;&lt;br /&gt;
        if (shouldSkip(href)) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        playStaticSound();&lt;br /&gt;
        /*&lt;br /&gt;
        SPA must remain a consumer phase.  If the target page HTML was prepared by&lt;br /&gt;
        the first-load boot pack, do not show the legacy page-loading veil.  The&lt;br /&gt;
        route will consume cached HTML and detached entry artifacts before insertion.&lt;br /&gt;
        */&lt;br /&gt;
        if (getCachedSpaPageHtml(href)) {&lt;br /&gt;
            $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;body&#039;).addClass(&#039;page-loading&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        history.pushState(null, &#039;&#039;, href);&lt;br /&gt;
        loadPage(href);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;popstate&#039;, function() {&lt;br /&gt;
        loadPage(window.location.href);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ========== CLBI Custom Document Scrollbar ========== */&lt;br /&gt;
function isGeneralDocumentView() {&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    if (!body) return false;&lt;br /&gt;
&lt;br /&gt;
    return body.classList.contains(&#039;action-view&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-main-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-system-doc-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;backend-system-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiDocumentScrollTargets() {&lt;br /&gt;
    if (!isGeneralDocumentView()) return [];&lt;br /&gt;
&lt;br /&gt;
    return Array.prototype.slice.call(document.querySelectorAll(&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; #mw-content-text .mw-parser-output, &#039; +&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; .mw-body-content .mw-parser-output&#039;&lt;br /&gt;
    )).filter(function (el, index, list) {&lt;br /&gt;
        return el &amp;amp;&amp;amp; list.indexOf(el) === index;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiOuterWellForScroll(scrollEl) {&lt;br /&gt;
    var main = scrollEl ? scrollEl.closest(&#039;.liberty-content-main&#039;) : null;&lt;br /&gt;
    var children;&lt;br /&gt;
    var i;&lt;br /&gt;
    var child;&lt;br /&gt;
&lt;br /&gt;
    if (!main) return null;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(main.children || []);&lt;br /&gt;
    for (i = 0; i &amp;lt; children.length; i += 1) {&lt;br /&gt;
        child = children[i];&lt;br /&gt;
        if (&lt;br /&gt;
            child &amp;amp;&amp;amp;&lt;br /&gt;
            (child.id === &#039;mw-content-text&#039; || child.classList.contains(&#039;mw-body-content&#039;)) &amp;amp;&amp;amp;&lt;br /&gt;
            child.contains(scrollEl)&lt;br /&gt;
        ) {&lt;br /&gt;
            return child;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return scrollEl.parentElement || null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiCustomScrollbar(well, scrollEl) {&lt;br /&gt;
    var bar = well.querySelector(&#039;:scope &amp;gt; .clbi-custom-scrollbar&#039;);&lt;br /&gt;
    var up;&lt;br /&gt;
    var track;&lt;br /&gt;
    var thumb;&lt;br /&gt;
    var down;&lt;br /&gt;
&lt;br /&gt;
    if (!bar) {&lt;br /&gt;
        bar = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bar.className = &#039;clbi-custom-scrollbar&#039;;&lt;br /&gt;
        bar.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        bar.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-up&amp;quot; data-scroll-arrow=&amp;quot;up&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-track&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;clbi-custom-scroll-thumb&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-down&amp;quot; data-scroll-arrow=&amp;quot;down&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        well.appendChild(bar);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.__clbiScrollTarget = scrollEl;&lt;br /&gt;
    up = bar.querySelector(&#039;.clbi-custom-scroll-arrow-up&#039;);&lt;br /&gt;
    track = bar.querySelector(&#039;.clbi-custom-scroll-track&#039;);&lt;br /&gt;
    thumb = bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;);&lt;br /&gt;
    down = bar.querySelector(&#039;.clbi-custom-scroll-arrow-down&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (up &amp;amp;&amp;amp; !up.__clbiBound) {&lt;br /&gt;
        up.__clbiBound = true;&lt;br /&gt;
        up.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop -= 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (down &amp;amp;&amp;amp; !down.__clbiBound) {&lt;br /&gt;
        down.__clbiBound = true;&lt;br /&gt;
        down.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop += 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (track &amp;amp;&amp;amp; !track.__clbiBound) {&lt;br /&gt;
        track.__clbiBound = true;&lt;br /&gt;
        track.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var rect;&lt;br /&gt;
            var thumbRect;&lt;br /&gt;
            var target;&lt;br /&gt;
            var direction;&lt;br /&gt;
&lt;br /&gt;
            if (e.target === thumb) return;&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            target = bar.__clbiScrollTarget;&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            rect = track.getBoundingClientRect();&lt;br /&gt;
            thumbRect = thumb.getBoundingClientRect();&lt;br /&gt;
            direction = e.clientY &amp;lt; thumbRect.top ? -1 : 1;&lt;br /&gt;
            target.scrollTop += direction * Math.max(60, Math.floor(target.clientHeight * 0.82));&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (thumb &amp;amp;&amp;amp; !thumb.__clbiBound) {&lt;br /&gt;
        thumb.__clbiBound = true;&lt;br /&gt;
        thumb.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var target = bar.__clbiScrollTarget;&lt;br /&gt;
            var startY;&lt;br /&gt;
            var startScroll;&lt;br /&gt;
            var maxScroll;&lt;br /&gt;
            var maxThumbTop;&lt;br /&gt;
            var trackHeight;&lt;br /&gt;
            var thumbHeight;&lt;br /&gt;
&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            startY = e.clientY;&lt;br /&gt;
            startScroll = target.scrollTop;&lt;br /&gt;
            maxScroll = Math.max(1, target.scrollHeight - target.clientHeight);&lt;br /&gt;
            trackHeight = track ? track.clientHeight : 0;&lt;br /&gt;
            thumbHeight = thumb.offsetHeight || 0;&lt;br /&gt;
            maxThumbTop = Math.max(1, trackHeight - thumbHeight);&lt;br /&gt;
&lt;br /&gt;
            bar.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
            function onMove(moveEvent) {&lt;br /&gt;
                var dy = moveEvent.clientY - startY;&lt;br /&gt;
                target.scrollTop = startScroll + (dy / maxThumbTop) * maxScroll;&lt;br /&gt;
                updateClbiCustomScrollbar(bar);&lt;br /&gt;
                moveEvent.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function onUp() {&lt;br /&gt;
                bar.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                document.removeEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
                document.removeEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            document.addEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
            document.addEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!scrollEl.__clbiCustomScrollbarBound) {&lt;br /&gt;
        scrollEl.__clbiCustomScrollbarBound = true;&lt;br /&gt;
        scrollEl.addEventListener(&#039;scroll&#039;, function () {&lt;br /&gt;
            if (scrollEl.__clbiCustomScrollbar) {&lt;br /&gt;
                updateClbiCustomScrollbar(scrollEl.__clbiCustomScrollbar);&lt;br /&gt;
            }&lt;br /&gt;
        }, { passive: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scrollEl.__clbiCustomScrollbar = bar;&lt;br /&gt;
    updateClbiCustomScrollbar(bar);&lt;br /&gt;
&lt;br /&gt;
    return bar;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiCustomScrollbar(bar) {&lt;br /&gt;
    var scrollEl = bar &amp;amp;&amp;amp; bar.__clbiScrollTarget;&lt;br /&gt;
    var track = bar ? bar.querySelector(&#039;.clbi-custom-scroll-track&#039;) : null;&lt;br /&gt;
    var thumb = bar ? bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;) : null;&lt;br /&gt;
    var maxScroll;&lt;br /&gt;
    var trackHeight;&lt;br /&gt;
    var thumbHeight;&lt;br /&gt;
    var maxTop;&lt;br /&gt;
    var top;&lt;br /&gt;
&lt;br /&gt;
    if (!bar || !scrollEl || !track || !thumb) return;&lt;br /&gt;
&lt;br /&gt;
    maxScroll = scrollEl.scrollHeight - scrollEl.clientHeight;&lt;br /&gt;
    if (maxScroll &amp;lt;= 1) {&lt;br /&gt;
        bar.classList.add(&#039;is-hidden&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.classList.remove(&#039;is-hidden&#039;);&lt;br /&gt;
&lt;br /&gt;
    trackHeight = Math.max(1, track.clientHeight || 1);&lt;br /&gt;
    thumbHeight = Math.max(12, Math.floor((scrollEl.clientHeight / Math.max(scrollEl.scrollHeight, 1)) * trackHeight));&lt;br /&gt;
    thumbHeight = Math.min(trackHeight, thumbHeight);&lt;br /&gt;
    maxTop = Math.max(0, trackHeight - thumbHeight);&lt;br /&gt;
    top = maxScroll &amp;gt; 0 ? Math.round((scrollEl.scrollTop / maxScroll) * maxTop) : 0;&lt;br /&gt;
&lt;br /&gt;
    thumb.style.height = thumbHeight + &#039;px&#039;;&lt;br /&gt;
    thumb.style.transform = &#039;translateY(&#039; + top + &#039;px)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiCustomDocumentScrollbars() {&lt;br /&gt;
    var existing = Array.prototype.slice.call(document.querySelectorAll(&#039;.clbi-custom-scrollbar&#039;));&lt;br /&gt;
    var targets = getClbiDocumentScrollTargets();&lt;br /&gt;
    var liveBars = [];&lt;br /&gt;
&lt;br /&gt;
    if (!targets.length) {&lt;br /&gt;
        existing.forEach(function (bar) { bar.remove(); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    targets.forEach(function (scrollEl) {&lt;br /&gt;
        var well = getClbiOuterWellForScroll(scrollEl);&lt;br /&gt;
        var bar;&lt;br /&gt;
&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
        bar = buildClbiCustomScrollbar(well, scrollEl);&lt;br /&gt;
        liveBars.push(bar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    existing.forEach(function (bar) {&lt;br /&gt;
        if (liveBars.indexOf(bar) === -1) bar.remove();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (!window.__clbiCustomScrollbarResizeBound) {&lt;br /&gt;
    window.__clbiCustomScrollbarResizeBound = true;&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        setTimeout(initClbiCustomDocumentScrollbars, 60);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시간 계산 함수&lt;br /&gt;
function timeAgo(timestamp) {&lt;br /&gt;
    var now = new Date();&lt;br /&gt;
    var date = new Date(timestamp);&lt;br /&gt;
    var diff = Math.floor((now - date) / 1000);&lt;br /&gt;
&lt;br /&gt;
    if (diff &amp;lt; 60) return diff + &#039;초 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 3600) return Math.floor(diff / 60) + &#039;분 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 86400) return Math.floor(diff / 3600) + &#039;시간 전&#039;;&lt;br /&gt;
    return Math.floor(diff / 86400) + &#039;일 전&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
function getFoldTexts() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return (window.LANG &amp;amp;&amp;amp; window.LANG[lang])&lt;br /&gt;
        ? window.LANG[lang]&lt;br /&gt;
        : (window.LANG ? window.LANG.ko : { expand: &#039;펼치기&#039;, collapse: &#039;접기&#039; });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function refreshOpenAncestors($start) {&lt;br /&gt;
    $start.parents(&#039;[id^=&amp;quot;collapsible&amp;quot;]&#039;).each(function () {&lt;br /&gt;
        var $parent = $(this);&lt;br /&gt;
        if (!$parent.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        // 이미 fully open 상태면 굳이 다시 잠그지 않음&lt;br /&gt;
        if ($parent.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $parent.css(&#039;max-height&#039;, this.scrollHeight + &#039;px&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindInnerResizeUpdates($target) {&lt;br /&gt;
    // 이미지 늦게 로드될 때 높이 갱신&lt;br /&gt;
    $target.find(&#039;img&#039;).off(&#039;.foldimg&#039;).on(&#039;load.foldimg&#039;, function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            if ($target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
                $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function openFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;opening&#039;);&lt;br /&gt;
    $target.addClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 열린 뒤 자연 확장 가능하게 만들기 위해 먼저 px로 열기&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.collapse);&lt;br /&gt;
&lt;br /&gt;
    bindInnerResizeUpdates($target);&lt;br /&gt;
&lt;br /&gt;
    // 바깥 펼접 즉시 갱신&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    // 전환 끝나면 none으로 풀어서 중첩 펼접/동적 내용 증가를 자연스럽게 허용&lt;br /&gt;
    $target.off(&#039;transitionend.foldopen&#039;).on(&#039;transitionend.foldopen&#039;, function (e) {&lt;br /&gt;
        if (e.target !== this) return;&lt;br /&gt;
        if (!$target.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        $target.css(&#039;max-height&#039;, &#039;none&#039;);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;open&#039;);&lt;br /&gt;
&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // 늦게 렌더되는 콘텐츠 대응&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 80);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 220);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function closeFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    // none 상태에서 닫으면 transition이 안 되므로 실제 높이로 고정&lt;br /&gt;
    if ($target.css(&#039;max-height&#039;) === &#039;none&#039; || $target.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;closing&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target.removeClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.expand);&lt;br /&gt;
&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;closed&#039;);&lt;br /&gt;
    }, 250);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function () {&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiToggle&#039;, &#039;.toggleBtn&#039;, function () {&lt;br /&gt;
            var $btn = $(this);&lt;br /&gt;
            var targetId = $btn.data(&#039;target&#039;);&lt;br /&gt;
            var $target = $(&#039;#&#039; + targetId);&lt;br /&gt;
            if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
            var scrollY = window.scrollY;&lt;br /&gt;
&lt;br /&gt;
            if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
                closeFold($target, $btn);&lt;br /&gt;
            } else {&lt;br /&gt;
                openFold($target, $btn);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            window.scrollTo(0, scrollY);&lt;br /&gt;
        });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// ========== 프로필 시스템 ==========&lt;br /&gt;
function initProfile() {&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var token = ++PROFILE_RENDER_TOKEN;&lt;br /&gt;
    var ns = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var title = mw.config.get(&#039;wgTitle&#039;);&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    var isProfileSettings = specialPage === &#039;사용자정보&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, ns === 2);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-settings-page&#039;, isProfileSettings);&lt;br /&gt;
&lt;br /&gt;
    if (ns === 2) {&lt;br /&gt;
        var profileUser = title.split(&#039;/&#039;)[0];&lt;br /&gt;
        renderProfile(profileUser, token);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isProfileSettings) {&lt;br /&gt;
        initUserProfilePage();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderProfile(username, token) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        list: &#039;users&#039;,&lt;br /&gt;
        ususers: username,&lt;br /&gt;
        usprop: &#039;editcount&#039;&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        if (token !== PROFILE_RENDER_TOKEN) return;&lt;br /&gt;
        if (mw.config.get(&#039;wgNamespaceNumber&#039;) !== 2) return;&lt;br /&gt;
&lt;br /&gt;
        var currentTitle = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).split(&#039;/&#039;)[0];&lt;br /&gt;
        if (currentTitle !== username) return;&lt;br /&gt;
&lt;br /&gt;
        var user = data.query.users[0];&lt;br /&gt;
        var contentEl = document.getElementById(&#039;mw-content-text&#039;);&lt;br /&gt;
        if (!contentEl) return;&lt;br /&gt;
&lt;br /&gt;
        var pageContent = contentEl.querySelector(&#039;.mw-parser-output&#039;) || contentEl;&lt;br /&gt;
        injectProfileCard(username, user, pageContent);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function injectProfileCard(username, userData, container) {&lt;br /&gt;
    var isOwnPage = mw.config.get(&#039;wgUserName&#039;) === username;&lt;br /&gt;
    var editCount = (userData &amp;amp;&amp;amp; userData.editcount) ? userData.editcount : 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.classList.add(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var safeUsername = escapeHtml(username);&lt;br /&gt;
    var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(username) + &#039;.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var fallbackSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var editBtn = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;a href=&amp;quot;/index.php/특수:사용자정보&amp;quot; class=&amp;quot;profile-edit-btn&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-label&amp;quot;&amp;gt;프로필 수정&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-arrow&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var progressHtml = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;div class=&amp;quot;profile-page-progress is-syncing&amp;quot; data-profile-progress&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;LEVEL RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-page-progress-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-level&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;profile-page-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-sub&amp;quot;&amp;gt;SYNCING&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-meta&amp;quot;&amp;gt;TODAY — · DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var card = document.createElement(&#039;div&#039;);&lt;br /&gt;
    card.className = &#039;profile-card profile-page-console&#039;;&lt;br /&gt;
    card.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-titlebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;USER PROFILE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;OFFICIAL ARCHIVE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-identity-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-avatar-bay&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;&#039; + fallbackSrc + &#039;\&#039;;&amp;quot; alt=&amp;quot;&#039; + safeUsername + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-info-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-nameplate&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;h2 class=&amp;quot;profile-username&amp;quot;&amp;gt;&#039; + safeUsername + &#039;&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
                        editBtn +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-name&amp;quot; data-field=&amp;quot;name&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-role&amp;quot; data-field=&amp;quot;role&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-discord&amp;quot; data-field=&amp;quot;discord&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-lower-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-bio-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;BIOGRAPHY&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-bio&amp;quot; data-field=&amp;quot;bio&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-stats-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-stats&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        progressHtml +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-stat-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot;&amp;gt;&#039; + editCount + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;수정 횟수&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot; data-contrib-pages-stat&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot; data-contrib-pages-value&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;기여 문서&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-info-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;TIME&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-time&amp;gt;UTC --:--&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;LANGUAGE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-language&amp;gt;—&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    container.insertBefore(card, container.firstChild);&lt;br /&gt;
    loadProfileFields(username, card);&lt;br /&gt;
    loadProfileContributionPages(username, card);&lt;br /&gt;
    updateProfilePageEnvironment(card, null);&lt;br /&gt;
&lt;br /&gt;
    if (isOwnPage) {&lt;br /&gt;
        loadProfileProgressForUserPage(card);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getProfileLanguageLabel() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || (lang ? lang.toUpperCase() : &#039;—&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfilePageEnvironment(card, summary) {&lt;br /&gt;
    if (!card) return;&lt;br /&gt;
&lt;br /&gt;
    var timezone = summary &amp;amp;&amp;amp; summary.timezone ? summary.timezone : &#039;UTC&#039;;&lt;br /&gt;
    var timeEl = card.querySelector(&#039;[data-profile-time]&#039;);&lt;br /&gt;
    var langEl = card.querySelector(&#039;[data-profile-language]&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (timeEl) {&lt;br /&gt;
        try {&lt;br /&gt;
            timeEl.textContent = timezone + &#039; &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: timezone&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            timeEl.textContent = &#039;UTC &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: &#039;UTC&#039;&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (langEl) {&lt;br /&gt;
        langEl.textContent = getProfileLanguageLabel();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileContributionPages(username, card) {&lt;br /&gt;
    if (!username || !card) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var valueEl = card.querySelector(&#039;[data-contrib-pages-value]&#039;);&lt;br /&gt;
    if (valueEl) valueEl.textContent = &#039;SYNC&#039;;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        var pages = Object.create(null);&lt;br /&gt;
        var cont = {};&lt;br /&gt;
        var guard = 0;&lt;br /&gt;
&lt;br /&gt;
        function requestNext() {&lt;br /&gt;
            guard++;&lt;br /&gt;
&lt;br /&gt;
            var params = Object.assign({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                list: &#039;usercontribs&#039;,&lt;br /&gt;
                ucuser: username,&lt;br /&gt;
                ucnamespace: 0,&lt;br /&gt;
                ucprop: &#039;title&#039;,&lt;br /&gt;
                uclimit: &#039;max&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }, cont);&lt;br /&gt;
&lt;br /&gt;
            return api.get(params).then(function (data) {&lt;br /&gt;
                var rows = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.usercontribs ? data.query.usercontribs : [];&lt;br /&gt;
&lt;br /&gt;
                rows.forEach(function (row) {&lt;br /&gt;
                    if (row &amp;amp;&amp;amp; row.title) pages[row.title] = true;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                if (data &amp;amp;&amp;amp; data.continue &amp;amp;&amp;amp; data.continue.uccontinue &amp;amp;&amp;amp; guard &amp;lt; 40) {&lt;br /&gt;
                    cont = data.continue;&lt;br /&gt;
                    return requestNext();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (valueEl) valueEl.textContent = Object.keys(pages).length;&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestNext().fail(function () {&lt;br /&gt;
            if (valueEl) valueEl.textContent = &#039;—&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileProgressForUserPage(card) {&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;progress_summary&#039;,&lt;br /&gt;
            format: &#039;json&#039;,&lt;br /&gt;
            formatversion: 2&lt;br /&gt;
        }).then(function (data) {&lt;br /&gt;
            var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
            if (!payload || !payload.available || !payload.summary) return;&lt;br /&gt;
            updateUserPageProgress(card, payload.summary);&lt;br /&gt;
            updateProfilePageEnvironment(card, payload.summary);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateUserPageProgress(card, summary) {&lt;br /&gt;
    var panel = card.querySelector(&#039;[data-profile-progress]&#039;);&lt;br /&gt;
    if (!panel || !summary) return;&lt;br /&gt;
&lt;br /&gt;
    var level = summary.level || 1;&lt;br /&gt;
    var totalXp = summary.totalXp || 0;&lt;br /&gt;
    var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
    var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
    var percent = Math.max(0, Math.min(100, summary.progressPercent || 0));&lt;br /&gt;
    var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
    var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
    var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
&lt;br /&gt;
    panel.classList.remove(&#039;is-syncing&#039;);&lt;br /&gt;
    panel.classList.toggle(&#039;is-max-level&#039;, isMaxLevel);&lt;br /&gt;
&lt;br /&gt;
    var levelEl = panel.querySelector(&#039;.profile-page-level&#039;);&lt;br /&gt;
    var totalEl = panel.querySelector(&#039;.profile-page-total-xp&#039;);&lt;br /&gt;
    var fillEl = panel.querySelector(&#039;.profile-page-xp-fill&#039;);&lt;br /&gt;
    var subEl = panel.querySelector(&#039;.profile-page-progress-sub&#039;);&lt;br /&gt;
    var metaEl = panel.querySelector(&#039;.profile-page-progress-meta&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (levelEl) levelEl.textContent = (isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level;&lt;br /&gt;
    if (totalEl) totalEl.textContent = totalXp + &#039; XP&#039;;&lt;br /&gt;
    if (fillEl) fillEl.style.width = percent + &#039;%&#039;;&lt;br /&gt;
    if (subEl) subEl.textContent = isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;);&lt;br /&gt;
    if (metaEl) metaEl.textContent = &#039;TODAY &#039; + dailyXp + &#039; XP · DISCOVERED &#039; + discoveries;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileFields(username, card) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;userprofile&#039;,&lt;br /&gt;
        user: username&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var profile = data.userprofile;&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: profile.name || &#039;&#039;,&lt;br /&gt;
            discord: profile.discord || &#039;&#039;,&lt;br /&gt;
            role: profile.role || &#039;&#039;,&lt;br /&gt;
            bio: profile.bio || &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    }).fail(function() {&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: &#039;&#039;,&lt;br /&gt;
            discord: &#039;&#039;,&lt;br /&gt;
            role: &#039;&#039;,&lt;br /&gt;
            bio: &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfileFields(card, data) {&lt;br /&gt;
    var nameEl = card.querySelector(&#039;[data-field=&amp;quot;name&amp;quot;]&#039;);&lt;br /&gt;
    var roleEl = card.querySelector(&#039;[data-field=&amp;quot;role&amp;quot;]&#039;);&lt;br /&gt;
    var discordEl = card.querySelector(&#039;[data-field=&amp;quot;discord&amp;quot;]&#039;);&lt;br /&gt;
    var bioEl = card.querySelector(&#039;[data-field=&amp;quot;bio&amp;quot;]&#039;);&lt;br /&gt;
    if (nameEl) nameEl.textContent = data.name || &#039;&#039;;&lt;br /&gt;
    if (roleEl) roleEl.textContent = data.role || &#039;&#039;;&lt;br /&gt;
    if (discordEl) discordEl.textContent = data.discord ? (&#039;디스코드: &#039; + data.discord) : &#039;&#039;;&lt;br /&gt;
    if (bioEl) bioEl.textContent = data.bio || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
// ========== 프로필 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
// ========== 알림 시스템 ==========&lt;br /&gt;
function ensureNotificationPopup() {&lt;br /&gt;
    if (document.getElementById(&#039;clbi-notification-popup&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    var popup = document.createElement(&#039;div&#039;);&lt;br /&gt;
    popup.id = &#039;clbi-notification-popup&#039;;&lt;br /&gt;
    popup.style.cssText =&lt;br /&gt;
        &#039;display:none;position:fixed;z-index:99999;width:320px;max-height:420px;&#039; +&lt;br /&gt;
        &#039;background:#0a0909;border:2px solid #854369;border-radius:5px;&#039; +&lt;br /&gt;
        &#039;box-shadow:0 0 0 1px #1a1a1a, 0 8px 24px rgba(0,0,0,0.55);overflow:hidden;&#039;;&lt;br /&gt;
&lt;br /&gt;
    popup.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:10px 12px;border-bottom:2px solid #854369;background:linear-gradient(to bottom, #171114 0%, #0a0909 100%);color:#E2E2E2;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:space-between;gap:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;알림&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-notification-readall&amp;quot; style=&amp;quot;background:#171717;border:1px solid #854369;border-radius:6px;color:#E2E2E2;font-size:11px;font-weight:700;padding:4px 8px;cursor:pointer;&amp;quot;&amp;gt;전체 읽음&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;clbi-notification-list&amp;quot; style=&amp;quot;max-height:320px;overflow-y:auto;padding:8px 0;color:#E2E2E2;font-size:12px;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:8px;border-top:1px solid #2a2a2a;background:#111;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;/index.php?title=Special:Notifications&amp;quot; id=&amp;quot;clbi-notification-more&amp;quot; style=&amp;quot;display:block;width:100%;text-align:center;padding:8px 10px;border-radius:6px;background:#171717;border:1px solid #854369;color:#E2E2E2 !important;text-decoration:none !important;font-size:12px;font-weight:700;&amp;quot;&amp;gt;더보기&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    document.body.appendChild(popup);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function positionNotificationPopup() {&lt;br /&gt;
    var btn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
    var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
    if (!btn || !popup) return;&lt;br /&gt;
&lt;br /&gt;
    var rect = btn.getBoundingClientRect();&lt;br /&gt;
    var top = rect.bottom + 6;&lt;br /&gt;
    var left = rect.left + (rect.width / 2) - (popup.offsetWidth / 2);&lt;br /&gt;
&lt;br /&gt;
    if (left &amp;lt; 8) left = 8;&lt;br /&gt;
    if (left + popup.offsetWidth &amp;gt; window.innerWidth - 8) {&lt;br /&gt;
        left = window.innerWidth - popup.offsetWidth - 8;&lt;br /&gt;
    }&lt;br /&gt;
    if (top + popup.offsetHeight &amp;gt; window.innerHeight - 8) {&lt;br /&gt;
        top = Math.max(8, rect.top - popup.offsetHeight - 6);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    popup.style.top = top + &#039;px&#039;;&lt;br /&gt;
    popup.style.left = left + &#039;px&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseNotificationItemsFromHtml(html) {&lt;br /&gt;
    var parser = new DOMParser();&lt;br /&gt;
    var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.mw-echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;.mw-echo-ui-notificationsInboxWidgetRow&#039;,&lt;br /&gt;
        &#039;.echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;li[data-notification-id]&#039;,&lt;br /&gt;
        &#039;.mw-echo-notifications-list li&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var items = [];&lt;br /&gt;
    for (var i = 0; i &amp;lt; selectors.length; i++) {&lt;br /&gt;
        items = Array.prototype.slice.call(doc.querySelectorAll(selectors[i]));&lt;br /&gt;
        if (items.length) break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return items.slice(0, 5).map(function(item) {&lt;br /&gt;
        var link = item.querySelector(&#039;a[href]&#039;);&lt;br /&gt;
        var href = link ? link.getAttribute(&#039;href&#039;) : &#039;/index.php?title=Special:Notifications&#039;;&lt;br /&gt;
        var text = (item.textContent || &#039;&#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        var notificationId =&lt;br /&gt;
            item.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
            &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!notificationId) {&lt;br /&gt;
            var anyWithId = item.querySelector(&#039;[data-notification-id], [data-id], [data-notification]&#039;);&lt;br /&gt;
            if (anyWithId) {&lt;br /&gt;
                notificationId =&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (href &amp;amp;&amp;amp; href.indexOf(&#039;http&#039;) !== 0) {&lt;br /&gt;
            href = href.charAt(0) === &#039;/&#039;&lt;br /&gt;
                ? href&lt;br /&gt;
                : &#039;/index.php&#039; + (href.charAt(0) === &#039;?&#039; ? href : &#039;/&#039; + href);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            id: notificationId,&lt;br /&gt;
            href: href,&lt;br /&gt;
            text: text || &#039;알림&#039;&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setNotificationIcon(hasItems) {&lt;br /&gt;
    var quickIcon = document.getElementById(&#039;profile-quick-notification-icon&#039;);&lt;br /&gt;
    var svg = hasItems ? CLBI_SVG_BELL_DOT : CLBI_SVG_BELL;&lt;br /&gt;
&lt;br /&gt;
    if (quickIcon) {&lt;br /&gt;
        quickIcon.innerHTML = svg;&lt;br /&gt;
        quickIcon.classList.toggle(&#039;has-notifications&#039;, !!hasItems);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderNotificationPopup(items) {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    var badge = document.getElementById(&#039;clbi-notification-badge&#039;);&lt;br /&gt;
    if (!list) return;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;표시할 알림이 없습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        if (badge) badge.style.display = &#039;none&#039;;&lt;br /&gt;
        setNotificationIcon(false);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
    for (var i = 0; i &amp;lt; items.length; i++) {&lt;br /&gt;
        html +=&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;&#039; + items[i].href + &#039;&amp;quot; class=&amp;quot;clbi-notification-item&amp;quot; data-notification-id=&amp;quot;&#039; + (items[i].id || &#039;&#039;) + &#039;&amp;quot; style=&amp;quot;display:block;padding:10px 12px;color:#E2E2E2 !important;text-decoration:none !important;border-bottom:1px solid #1f1f1f;line-height:1.5;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                items[i].text +&lt;br /&gt;
            &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    list.innerHTML = html;&lt;br /&gt;
&lt;br /&gt;
    if (badge) {&lt;br /&gt;
        badge.textContent = items.length;&lt;br /&gt;
        badge.style.display = &#039;block&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    setNotificationIcon(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadNotificationsIntoPopup() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetch(&#039;/index.php?title=Special:Notifications&#039;, { credentials: &#039;same-origin&#039; })&lt;br /&gt;
        .then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        })&lt;br /&gt;
        .then(function(html) {&lt;br /&gt;
            var items = parseNotificationItemsFromHtml(html);&lt;br /&gt;
            renderNotificationPopup(items);&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function(err) {&lt;br /&gt;
            console.error(err);&lt;br /&gt;
            if (list) {&lt;br /&gt;
                list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;알림을 불러오지 못했습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markAllNotificationsRead() {&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: &#039;all&#039;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markNotificationReadById(notificationId) {&lt;br /&gt;
    if (!notificationId) {&lt;br /&gt;
        return $.Deferred().resolve().promise();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: notificationId&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initNotifications() {&lt;br /&gt;
    var quickBtn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!quickBtn) return;&lt;br /&gt;
&lt;br /&gt;
    ensureNotificationPopup();&lt;br /&gt;
    loadNotificationsIntoPopup();&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationToggle&#039;, &#039;#profile-quick-notifications&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (popup.style.display === &#039;none&#039; || popup.style.display === &#039;&#039;) {&lt;br /&gt;
                popup.style.display = &#039;block&#039;;&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
            } else {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationOutside&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationOutside&#039;, function(e) {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            var quickToggle = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (!popup.contains(e.target) &amp;amp;&amp;amp; (!quickToggle || !quickToggle.contains(e.target))) {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationReadAll&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationReadAll&#039;, &#039;#clbi-notification-readall&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var button = this;&lt;br /&gt;
            button.disabled = true;&lt;br /&gt;
            button.textContent = &#039;처리 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            markAllNotificationsRead()&lt;br /&gt;
                .then(function() {&lt;br /&gt;
                    loadNotificationsIntoPopup();&lt;br /&gt;
                })&lt;br /&gt;
                .always(function() {&lt;br /&gt;
                    button.disabled = false;&lt;br /&gt;
                    button.textContent = &#039;전체 읽음&#039;;&lt;br /&gt;
                });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationItem&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationItem&#039;, &#039;.clbi-notification-item&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var href = this.getAttribute(&#039;href&#039;);&lt;br /&gt;
            var notificationId = this.getAttribute(&#039;data-notification-id&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            markNotificationReadById(notificationId).always(function() {&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
                if (href) {&lt;br /&gt;
                    window.location.href = href;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(window)&lt;br /&gt;
        .off(&#039;resize.clbiNotification&#039;)&lt;br /&gt;
        .on(&#039;resize.clbiNotification&#039;, function() {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (popup &amp;amp;&amp;amp; popup.style.display === &#039;block&#039;) {&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
// ========== 알림 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
function initUserProfilePage() {&lt;br /&gt;
    $(&#039;body&#039;).addClass(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
&lt;br /&gt;
    var saveBtn = document.getElementById(&#039;pref-save&#039;);&lt;br /&gt;
    if (!saveBtn) return;&lt;br /&gt;
&lt;br /&gt;
    function getPrefRow(id) {&lt;br /&gt;
        var el = document.getElementById(id);&lt;br /&gt;
        if (!el) return null;&lt;br /&gt;
        return el.closest(&#039;.clbi-pref-row&#039;) || el.parentNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removePrefRow(id) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (row &amp;amp;&amp;amp; row.parentNode) {&lt;br /&gt;
            row.parentNode.removeChild(row);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPrefSection(className, titleText) {&lt;br /&gt;
        var section = document.createElement(&#039;div&#039;);&lt;br /&gt;
        section.className = &#039;clbi-pref-section &#039; + className;&lt;br /&gt;
&lt;br /&gt;
        var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;clbi-pref-section-title&#039;;&lt;br /&gt;
        title.textContent = titleText;&lt;br /&gt;
&lt;br /&gt;
        var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
        body.className = &#039;clbi-pref-section-body&#039;;&lt;br /&gt;
&lt;br /&gt;
        section.appendChild(title);&lt;br /&gt;
        section.appendChild(body);&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            section: section,&lt;br /&gt;
            body: body&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveRowToSection(id, targetBody, className) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (!row || !targetBody) return false;&lt;br /&gt;
&lt;br /&gt;
        row.classList.add(&#039;clbi-pref-row-key-&#039; + className);&lt;br /&gt;
        targetBody.appendChild(row);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebuildProfileSettingsLayout() {&lt;br /&gt;
        var root = document.querySelector(&#039;.clbi-prefs-profile&#039;);&lt;br /&gt;
        if (!root || root.dataset.profileSettingsReworked === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        root.dataset.profileSettingsReworked = &#039;1&#039;;&lt;br /&gt;
        root.classList.add(&#039;profile-settings-console&#039;);&lt;br /&gt;
&lt;br /&gt;
        removePrefRow(&#039;pref-badges&#039;);&lt;br /&gt;
&lt;br /&gt;
        var originalRows = Array.prototype.slice.call(root.querySelectorAll(&#039;.clbi-pref-row&#039;));&lt;br /&gt;
        var actionNodes = [];&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn.parentNode === root || saveBtn.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(saveBtn);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var statusNode = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        if (statusNode &amp;amp;&amp;amp; statusNode.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(statusNode);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var main = document.createElement(&#039;div&#039;);&lt;br /&gt;
        main.className = &#039;clbi-pref-main-grid&#039;;&lt;br /&gt;
&lt;br /&gt;
        var media = createPrefSection(&#039;clbi-pref-section-media&#039;, &#039;PROFILE IMAGE&#039;);&lt;br /&gt;
        var identity = createPrefSection(&#039;clbi-pref-section-identity&#039;, &#039;IDENTITY RECORD&#039;);&lt;br /&gt;
        var bio = createPrefSection(&#039;clbi-pref-section-bio&#039;, &#039;BIOGRAPHY&#039;);&lt;br /&gt;
        var account = createPrefSection(&#039;clbi-pref-section-account&#039;, &#039;ACCOUNT CONTACT&#039;);&lt;br /&gt;
        var misc = createPrefSection(&#039;clbi-pref-section-misc&#039;, &#039;OTHER OPTIONS&#039;);&lt;br /&gt;
&lt;br /&gt;
        main.appendChild(media.section);&lt;br /&gt;
        main.appendChild(identity.section);&lt;br /&gt;
        main.appendChild(bio.section);&lt;br /&gt;
        main.appendChild(account.section);&lt;br /&gt;
        main.appendChild(misc.section);&lt;br /&gt;
&lt;br /&gt;
        root.innerHTML = &#039;&#039;;&lt;br /&gt;
        root.appendChild(main);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-preview&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-btn&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-input&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-name&#039;, identity.body, &#039;name&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-role&#039;, identity.body, &#039;role&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-discord&#039;, identity.body, &#039;discord&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-bio&#039;, bio.body, &#039;bio&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-new-email&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-password&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-save&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
&lt;br /&gt;
        originalRows.forEach(function (row) {&lt;br /&gt;
            if (!row.parentNode &amp;amp;&amp;amp; !row.className.match(/clbi-pref-row-key-/)) {&lt;br /&gt;
                misc.body.appendChild(row);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!misc.body.children.length) {&lt;br /&gt;
            misc.section.parentNode.removeChild(misc.section);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var actions = document.createElement(&#039;div&#039;);&lt;br /&gt;
        actions.className = &#039;clbi-pref-actions&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn) actions.appendChild(saveBtn);&lt;br /&gt;
        if (statusNode) actions.appendChild(statusNode);&lt;br /&gt;
&lt;br /&gt;
        root.appendChild(actions);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    rebuildProfileSettingsLayout();&lt;br /&gt;
&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    var selectedFile = null;&lt;br /&gt;
    var cropper = null;&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-gallery-modal&#039;)) {&lt;br /&gt;
        var gModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        gModal.id = &#039;clbi-gallery-modal&#039;;&lt;br /&gt;
        gModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        gModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:480px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;justify-content:space-between;align-items:center;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;프로필 사진 선택&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-close&amp;quot; style=&amp;quot;background:none;border:none;color:#aaa;font-size:18px;cursor:pointer;&amp;quot;&amp;gt;✕&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-upload-btn&amp;quot; style=&amp;quot;background:#2a2a2a;border:2px dashed #854369;border-radius:8px;padding:32px;color:#e2e2e2;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:8px;font-size:13px;width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:32px;&amp;quot;&amp;gt;🖼️&amp;lt;/span&amp;gt;새 사진 업로드&#039; +&lt;br /&gt;
                &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history-section&amp;quot; style=&amp;quot;display:none;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div style=&amp;quot;font-size:11px;color:#888;margin-bottom:8px;&amp;quot;&amp;gt;이전 사진 — 클릭하면 바로 적용&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history&amp;quot; style=&amp;quot;display:flex;gap:8px;flex-wrap:wrap;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(gModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-crop-modal&#039;)) {&lt;br /&gt;
        var cModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        cModal.id = &#039;clbi-crop-modal&#039;;&lt;br /&gt;
        cModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        cModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:500px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;사진 조정&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;width:100%;max-height:380px;overflow:hidden;border-radius:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img id=&amp;quot;clbi-crop-image&amp;quot; style=&amp;quot;max-width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;gap:8px;justify-content:flex-end;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-cancel&amp;quot; style=&amp;quot;background:#2a2a2a;color:#e2e2e2;border:1px solid #444;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;취소&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-confirm&amp;quot; style=&amp;quot;background:#854369;color:#fff;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;확정&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(cModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gModal = document.getElementById(&#039;clbi-gallery-modal&#039;);&lt;br /&gt;
    var cModal = document.getElementById(&#039;clbi-crop-modal&#039;);&lt;br /&gt;
    var cropImage = document.getElementById(&#039;clbi-crop-image&#039;);&lt;br /&gt;
    var pfpInput = document.getElementById(&#039;pref-pfp-input&#039;);&lt;br /&gt;
&lt;br /&gt;
    function openGallery() {&lt;br /&gt;
        gModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;query&#039;,&lt;br /&gt;
            titles: &#039;파일:Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
            prop: &#039;imageinfo&#039;,&lt;br /&gt;
            iiprop: &#039;url|timestamp&#039;,&lt;br /&gt;
            iilimit: 6&lt;br /&gt;
        }).then(function(data) {&lt;br /&gt;
            var pages = data.query.pages;&lt;br /&gt;
            var page = pages[Object.keys(pages)[0]];&lt;br /&gt;
            if (!page.imageinfo || page.imageinfo.length === 0) return;&lt;br /&gt;
&lt;br /&gt;
            var historyEl = document.getElementById(&#039;clbi-gallery-history&#039;);&lt;br /&gt;
            var sectionEl = document.getElementById(&#039;clbi-gallery-history-section&#039;);&lt;br /&gt;
            historyEl.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            page.imageinfo.forEach(function(info, idx) {&lt;br /&gt;
                var wrap = document.createElement(&#039;div&#039;);&lt;br /&gt;
                wrap.style.cssText = &#039;position:relative;cursor:pointer;&#039;;&lt;br /&gt;
&lt;br /&gt;
                var img = document.createElement(&#039;img&#039;);&lt;br /&gt;
                img.src = info.url;&lt;br /&gt;
                img.style.cssText =&lt;br /&gt;
                    &#039;width:72px;height:72px;object-fit:cover;border-radius:8px;border:2px solid #444;flex-shrink:0;&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (idx === 0) {&lt;br /&gt;
                    img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                    var badge = document.createElement(&#039;div&#039;);&lt;br /&gt;
                    badge.textContent = &#039;현재&#039;;&lt;br /&gt;
                    badge.style.cssText =&lt;br /&gt;
                        &#039;position:absolute;bottom:4px;left:50%;transform:translateX(-50%);background:#854369;color:#fff;font-size:9px;padding:1px 6px;border-radius:10px;&#039;;&lt;br /&gt;
                    wrap.appendChild(badge);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseenter&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseleave&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#444&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
                    fetch(info.url)&lt;br /&gt;
                        .then(function(r) {&lt;br /&gt;
                            return r.blob();&lt;br /&gt;
                        })&lt;br /&gt;
                        .then(function(blob) {&lt;br /&gt;
                            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
                            gModal.style.display = &#039;none&#039;;&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                wrap.appendChild(img);&lt;br /&gt;
                historyEl.appendChild(wrap);&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            sectionEl.style.display = &#039;block&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function openCrop(src) {&lt;br /&gt;
        cropImage.src = src;&lt;br /&gt;
        cModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            cropper = new Cropper(cropImage, {&lt;br /&gt;
                aspectRatio: 1,&lt;br /&gt;
                viewMode: 1,&lt;br /&gt;
                dragMode: &#039;move&#039;,&lt;br /&gt;
                autoCropArea: 0.8,&lt;br /&gt;
                cropBoxResizable: true,&lt;br /&gt;
                cropBoxMovable: true&lt;br /&gt;
            });&lt;br /&gt;
        }, 150);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;pref-pfp-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        openGallery();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-upload-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        pfpInput.click();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-close&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    pfpInput.addEventListener(&#039;change&#039;, function() {&lt;br /&gt;
        var file = this.files[0];&lt;br /&gt;
        if (!file) return;&lt;br /&gt;
&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
&lt;br /&gt;
        var reader = new FileReader();&lt;br /&gt;
        reader.onload = function(e) {&lt;br /&gt;
            openCrop(e.target.result);&lt;br /&gt;
        };&lt;br /&gt;
        reader.readAsDataURL(file);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-cancel&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        cModal.style.display = &#039;none&#039;;&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
        pfpInput.value = &#039;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-confirm&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        if (!cropper) return;&lt;br /&gt;
&lt;br /&gt;
        var canvas = cropper.getCroppedCanvas({ width: 256, height: 256 });&lt;br /&gt;
        if (!canvas) return;&lt;br /&gt;
&lt;br /&gt;
        canvas.toBlob(function(blob) {&lt;br /&gt;
            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
            cModal.style.display = &#039;none&#039;;&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
        }, &#039;image/png&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var emailSaveBtn = document.getElementById(&#039;pref-email-save&#039;);&lt;br /&gt;
    if (emailSaveBtn) {&lt;br /&gt;
        emailSaveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
            var statusEl = document.getElementById(&#039;pref-email-status&#039;);&lt;br /&gt;
            var newEmail = document.getElementById(&#039;pref-new-email&#039;).value;&lt;br /&gt;
            var password = document.getElementById(&#039;pref-email-password&#039;).value;&lt;br /&gt;
&lt;br /&gt;
            if (!newEmail || !password) {&lt;br /&gt;
                statusEl.textContent = &#039;이메일과 비밀번호를 입력해주세요.&#039;;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            statusEl.textContent = &#039;변경 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;changeemail&#039;,&lt;br /&gt;
                email: newEmail,&lt;br /&gt;
                password: password&lt;br /&gt;
            }).then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 이메일 변경됨&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-new-email&#039;).value = &#039;&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-email-password&#039;).value = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 3000);&lt;br /&gt;
            }).fail(function(code, data) {&lt;br /&gt;
                var msg = data &amp;amp;&amp;amp; data.error &amp;amp;&amp;amp; data.error.info ? data.error.info : &#039;변경 실패&#039;;&lt;br /&gt;
                statusEl.textContent = msg;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    saveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        var statusEl = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        statusEl.textContent = &#039;저장 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
        var promises = [];&lt;br /&gt;
&lt;br /&gt;
        if (selectedFile) {&lt;br /&gt;
            var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;upload&#039;,&lt;br /&gt;
                    filename: &#039;Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
                    ignorewarnings: true,&lt;br /&gt;
                    file: selectedFile,&lt;br /&gt;
                    format: &#039;json&#039;&lt;br /&gt;
                }, {&lt;br /&gt;
                    contentType: &#039;multipart/form-data&#039;&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var fields = [&#039;name&#039;, &#039;discord&#039;, &#039;role&#039;, &#039;bio&#039;];&lt;br /&gt;
&lt;br /&gt;
        for (var i = 0; i &amp;lt; fields.length; i++) {&lt;br /&gt;
            var el = document.getElementById(&#039;pref-&#039; + fields[i]);&lt;br /&gt;
            if (!el) continue;&lt;br /&gt;
&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;options&#039;,&lt;br /&gt;
                    optionname: &#039;profile-&#039; + fields[i],&lt;br /&gt;
                    optionvalue: el.value&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $.when.apply($, promises)&lt;br /&gt;
            .then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 저장됨&#039;;&lt;br /&gt;
                selectedFile = null;&lt;br /&gt;
                document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;사진 선택&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 2000);&lt;br /&gt;
            })&lt;br /&gt;
            .fail(function() {&lt;br /&gt;
                statusEl.textContent = &#039;저장 실패&#039;;&lt;br /&gt;
            });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Banner / CRT Page Monitor thumbnail slices&lt;br /&gt;
   - base 이미지는 틀에 들어간 파일 문법 그대로 사용&lt;br /&gt;
   - slice 레이어에는 300px MediaWiki 썸네일만 삽입&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function ($, mw) {&lt;br /&gt;
    var thumbCache = {};&lt;br /&gt;
&lt;br /&gt;
    function parseSliceWidth(value) {&lt;br /&gt;
        var parsed = parseInt(value, 10);&lt;br /&gt;
&lt;br /&gt;
        if (!isFinite(parsed) || parsed &amp;lt; 120) {&lt;br /&gt;
            return 300;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parsed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getImageSrc(img) {&lt;br /&gt;
        return img ? (img.currentSrc || img.getAttribute(&#039;src&#039;) || img.src || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFileNameFromSrc(src) {&lt;br /&gt;
        var a;&lt;br /&gt;
        var parts;&lt;br /&gt;
        var fileName;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = src;&lt;br /&gt;
&lt;br /&gt;
        parts = (a.pathname || &#039;&#039;).split(&#039;/&#039;).filter(function (part) {&lt;br /&gt;
            return !!part;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!parts.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        fileName = parts.pop();&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * MediaWiki thumb URL 예시:&lt;br /&gt;
         * /images/thumb/a/ab/File.png/1000px-File.png&lt;br /&gt;
         * /images/thumb/a/ab/File.svg/1000px-File.svg.png&lt;br /&gt;
         *&lt;br /&gt;
         * 이 경우 실제 파일명은 마지막 조각이 아니라 그 앞 조각이다.&lt;br /&gt;
         */&lt;br /&gt;
        if (/^\d+px-/.test(fileName) &amp;amp;&amp;amp; parts.length) {&lt;br /&gt;
            fileName = parts.pop();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fileName = fileName.replace(/^\d+px-/, &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            fileName = decodeURIComponent(fileName);&lt;br /&gt;
        } catch (e) {}&lt;br /&gt;
&lt;br /&gt;
        return fileName;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveThumbUrl(img, width, callback) {&lt;br /&gt;
        var src = getImageSrc(img);&lt;br /&gt;
        var fileName = getFileNameFromSrc(src);&lt;br /&gt;
        var cacheKey;&lt;br /&gt;
        var entry;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return;&lt;br /&gt;
&lt;br /&gt;
        if (!fileName || !mw || !mw.loader) {&lt;br /&gt;
            callback(src);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        cacheKey = fileName + &#039;|&#039; + width;&lt;br /&gt;
        entry = thumbCache[cacheKey];&lt;br /&gt;
&lt;br /&gt;
        if (entry) {&lt;br /&gt;
            if (entry.resolved) {&lt;br /&gt;
                callback(entry.url || src);&lt;br /&gt;
            } else {&lt;br /&gt;
                entry.callbacks.push(callback);&lt;br /&gt;
            }&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        entry = {&lt;br /&gt;
            resolved: false,&lt;br /&gt;
            url: &#039;&#039;,&lt;br /&gt;
            callbacks: [callback]&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        thumbCache[cacheKey] = entry;&lt;br /&gt;
&lt;br /&gt;
        function finish(url) {&lt;br /&gt;
            var callbacks = entry.callbacks.slice();&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            entry.resolved = true;&lt;br /&gt;
            entry.url = url || src;&lt;br /&gt;
            entry.callbacks = [];&lt;br /&gt;
&lt;br /&gt;
            for (i = 0; i &amp;lt; callbacks.length; i++) {&lt;br /&gt;
                callbacks[i](entry.url);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using(&#039;mediawiki.api&#039;).done(function () {&lt;br /&gt;
            var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + fileName,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url&#039;,&lt;br /&gt;
                iiurlwidth: width,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).done(function (data) {&lt;br /&gt;
                var page;&lt;br /&gt;
                var info;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    data &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages.length&lt;br /&gt;
                ) {&lt;br /&gt;
                    page = data.query.pages[0];&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        page &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo.length&lt;br /&gt;
                    ) {&lt;br /&gt;
                        info = page.imageinfo[0];&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                finish((info &amp;amp;&amp;amp; (info.thumburl || info.url)) || src);&lt;br /&gt;
            }).fail(function () {&lt;br /&gt;
                finish(src);&lt;br /&gt;
            });&lt;br /&gt;
        }).fail(function () {&lt;br /&gt;
            finish(src);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySliceImages(frame, thumbUrl) {&lt;br /&gt;
        var slices;&lt;br /&gt;
        var i;&lt;br /&gt;
        var img;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !thumbUrl) return;&lt;br /&gt;
&lt;br /&gt;
        slices = frame.querySelectorAll(&#039;.crt-page-monitor-slice&#039;);&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; slices.length; i++) {&lt;br /&gt;
            slices[i].innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            img = document.createElement(&#039;img&#039;);&lt;br /&gt;
            img.className = &#039;crt-page-monitor-slice-img&#039;;&lt;br /&gt;
            img.src = thumbUrl;&lt;br /&gt;
            img.alt = &#039;&#039;;&lt;br /&gt;
            img.decoding = &#039;async&#039;;&lt;br /&gt;
            img.loading = &#039;eager&#039;;&lt;br /&gt;
            img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
            slices[i].appendChild(img);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        frame.setAttribute(&#039;data-crt-slices-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrame(frame) {&lt;br /&gt;
        var baseImg;&lt;br /&gt;
        var width;&lt;br /&gt;
&lt;br /&gt;
        if (!frame) return;&lt;br /&gt;
        if (frame.getAttribute(&#039;data-crt-slices-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        baseImg = frame.querySelector(&#039;.crt-page-monitor-image-base img&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!baseImg) return;&lt;br /&gt;
&lt;br /&gt;
        width = parseSliceWidth(frame.getAttribute(&#039;data-crt-slice-width&#039;));&lt;br /&gt;
&lt;br /&gt;
        resolveThumbUrl(baseImg, width, function (thumbUrl) {&lt;br /&gt;
            if (!frame || !frame.parentNode) return;&lt;br /&gt;
            applySliceImages(frame, thumbUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrames(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var frames = scope.querySelectorAll(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; frames.length; i++) {&lt;br /&gt;
            initBannerFrame(frames[i]);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initBannerFrames(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initBannerFrames($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(jQuery, window.mw);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Tab System — tab switching UI&lt;br /&gt;
   글리치 플리커 + RGB split + 방향 슬라이드&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function initDocTabs() {&lt;br /&gt;
        var tabBars = document.querySelectorAll(&#039;.doc-tab-bar&#039;);&lt;br /&gt;
        if (!tabBars.length) return;&lt;br /&gt;
&lt;br /&gt;
        tabBars.forEach(function (bar) {&lt;br /&gt;
            if (bar.getAttribute(&#039;data-tabs-init&#039;)) return;&lt;br /&gt;
            bar.setAttribute(&#039;data-tabs-init&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            var tabs = Array.from(bar.querySelectorAll(&#039;.doc-tab&#039;));&lt;br /&gt;
            if (!tabs.length) return;&lt;br /&gt;
&lt;br /&gt;
            var panel = bar.closest(&#039;.doc-panel&#039;);&lt;br /&gt;
            var display = panel ? panel.querySelector(&#039;.doc-display&#039;) : null;&lt;br /&gt;
            if (!display) display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
            if (!display) return;&lt;br /&gt;
&lt;br /&gt;
            tabs.forEach(function (tab, i) {&lt;br /&gt;
                tab.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
                    var currentIdx = tabs.findIndex(function (t) {&lt;br /&gt;
                        return t.classList.contains(&#039;active&#039;);&lt;br /&gt;
                    });&lt;br /&gt;
                    if (currentIdx === i) return;&lt;br /&gt;
                    switchTab(tabs, display, i, i &amp;gt; currentIdx ? 1 : -1);&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            var initIdx = tabs.findIndex(function (t) { return t.classList.contains(&#039;active&#039;); });&lt;br /&gt;
            if (initIdx !== -1) {&lt;br /&gt;
                var initRef = tabs[initIdx].dataset.ref;&lt;br /&gt;
                var initEl = initRef ? document.getElementById(initRef) : null;&lt;br /&gt;
                display.innerHTML = initEl ? initEl.innerHTML : (tabs[initIdx].dataset.content || &#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var isAnimating = false;&lt;br /&gt;
&lt;br /&gt;
    function switchTab(tabs, display, nextIdx, dir) {&lt;br /&gt;
        if (isAnimating) return;&lt;br /&gt;
        isAnimating = true;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (t) { t.classList.remove(&#039;active&#039;); });&lt;br /&gt;
        tabs[nextIdx].classList.add(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
        var ref = tabs[nextIdx].dataset.ref;&lt;br /&gt;
        var nextContent;&lt;br /&gt;
        if (ref) {&lt;br /&gt;
            var refEl = document.getElementById(ref);&lt;br /&gt;
            nextContent = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            nextContent = tabs[nextIdx].dataset.content || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        glitchOut(display, dir, function () {&lt;br /&gt;
            display.innerHTML = nextContent;&lt;br /&gt;
            glitchIn(display, dir, function () {&lt;br /&gt;
                isAnimating = false;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchOut(el, dir, cb) {&lt;br /&gt;
        var duration = 160;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var slideX = dir * 16;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = p * p;&lt;br /&gt;
&lt;br /&gt;
            var tx = slideX * ease;&lt;br /&gt;
            var skew = dir * ease * 1.0;&lt;br /&gt;
            var opacity = 1 - ease;&lt;br /&gt;
            var rgb = ease * 5;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.75)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.65)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + (1 + ease * 0.25) + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.opacity = &#039;0&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchIn(el, dir, cb) {&lt;br /&gt;
        var duration = 200;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var startX = -dir * 16;&lt;br /&gt;
&lt;br /&gt;
        el.style.transform = &#039;translateX(&#039; + startX + &#039;px) skewX(&#039; + (-dir * 1.0) + &#039;deg)&#039;;&lt;br /&gt;
        el.style.opacity = &#039;0&#039;;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = 1 - Math.pow(1 - p, 3);&lt;br /&gt;
&lt;br /&gt;
            var tx = startX * (1 - ease);&lt;br /&gt;
            var skew = -dir * 1.0 * (1 - ease);&lt;br /&gt;
            var opacity = ease;&lt;br /&gt;
            var rgb = (1 - ease) * 3;&lt;br /&gt;
            var brightness = 1 + (1 - ease) * 0.35;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.65)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.55)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + brightness + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.transform = &#039;&#039;;&lt;br /&gt;
                el.style.opacity = &#039;&#039;;&lt;br /&gt;
                el.style.filter = &#039;&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initDocTabs);&lt;br /&gt;
} else {&lt;br /&gt;
    initDocTabs();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        initDocTabs();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Section Switch — 좌측 섹션 전환&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;.doc-nav-item[data-section]&#039;, function () {&lt;br /&gt;
    var name = $(this).attr(&#039;data-section&#039;);&lt;br /&gt;
    var display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
    var titleEl = document.getElementById(&#039;doc-center-title&#039;);&lt;br /&gt;
    var tabBar = document.getElementById(&#039;doc-tab-bar-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!display) return;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section]&#039;).removeClass(&#039;active&#039;);&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section=&amp;quot;&#039; + name + &#039;&amp;quot;]&#039;).addClass(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (name === &#039;text&#039;) {&lt;br /&gt;
        if (titleEl) titleEl.textContent = &#039;개요&#039;;&lt;br /&gt;
        if (tabBar) $(tabBar).show();&lt;br /&gt;
        var activeTab = tabBar ? tabBar.querySelector(&#039;.doc-tab.active&#039;) : null;&lt;br /&gt;
        if (!activeTab &amp;amp;&amp;amp; tabBar) activeTab = tabBar.querySelector(&#039;.doc-tab&#039;);&lt;br /&gt;
        if (activeTab) {&lt;br /&gt;
            var ref = activeTab.dataset.ref;&lt;br /&gt;
            var refEl = ref ? document.getElementById(ref) : null;&lt;br /&gt;
            display.innerHTML = refEl ? refEl.innerHTML : (activeTab.dataset.content || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        if (titleEl) titleEl.textContent = name === &#039;factions&#039; ? &#039;세력&#039; : name === &#039;people&#039; ? &#039;인물&#039; : name;&lt;br /&gt;
        if (tabBar) $(tabBar).hide();&lt;br /&gt;
        var refEl = document.getElementById(&#039;doc-content-&#039; + name);&lt;br /&gt;
        display.innerHTML = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   CRT WebGL Renderer — cool-retro-term IBM DOS style&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function createNoiseTexture(gl) {&lt;br /&gt;
        var size = 512;&lt;br /&gt;
        var data = new Uint8Array(size * size * 4);&lt;br /&gt;
        var s = 12345;&lt;br /&gt;
        function rand() {&lt;br /&gt;
            s = (s * 1664525 + 1013904223) &amp;amp; 0xffffffff;&lt;br /&gt;
            return (s &amp;gt;&amp;gt;&amp;gt; 0) / 0xffffffff;&lt;br /&gt;
        }&lt;br /&gt;
        for (var i = 0; i &amp;lt; data.length; i++) {&lt;br /&gt;
            data[i] = (rand() * 255) | 0;&lt;br /&gt;
        }&lt;br /&gt;
        var tex = gl.createTexture();&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, tex);&lt;br /&gt;
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
        return tex;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var VERT = [&lt;br /&gt;
        &#039;attribute vec2 a_pos;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5);&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_pos, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var FRAG = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_tex;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_noise;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_res;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_imgSize;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_noiseScale;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float sum2(vec2 v) { return v.x + v.y; }&#039;,&lt;br /&gt;
        &#039;float min2(vec2 v) { return min(v.x, v.y); }&#039;,&lt;br /&gt;
        &#039;float rgb2grey(vec3 v) { return dot(v, vec3(0.21, 0.72, 0.04)); }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 coverUV(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  float imgAR = u_imgSize.x / u_imgSize.y;&#039;,&lt;br /&gt;
        &#039;  float scrAR = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  float scale = imgAR / scrAR;&#039;,&lt;br /&gt;
        &#039;  float offsetY = (1.0 - scale) * 0.5;&#039;,&lt;br /&gt;
        &#039;  return vec2(uv.x, uv.y * scale + offsetY);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 barrel(vec2 v, vec2 cc, float k) {&#039;,&lt;br /&gt;
        &#039;  float ar = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  vec2 c2 = cc;&#039;,&lt;br /&gt;
        &#039;  if (ar &amp;gt; 1.0) c2.x /= ar; else c2.y *= ar;&#039;,&lt;br /&gt;
        &#039;  float dist = dot(c2, c2) * k;&#039;,&lt;br /&gt;
        &#039;  return v - cc * (1.0 + dist) * dist;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleInitialNoise(float t) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, vec2(fract(t/2048.0), fract(t/1048576.0)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleScreenNoise(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, u_noiseScale * uv);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyRgbShift(vec2 texUV, float shift) {&#039;,&lt;br /&gt;
        &#039;  vec2 d = vec2(shift, 0.0);&#039;,&lt;br /&gt;
        &#039;  vec3 r = texture2D(u_tex, clamp(texUV + d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 c = texture2D(u_tex, texUV).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 l = texture2D(u_tex, clamp(texUV - d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  return vec3(&#039;,&lt;br /&gt;
        &#039;    l.r*0.10 + r.r*0.30 + c.r*0.60,&#039;,&lt;br /&gt;
        &#039;    l.g*0.20 + r.g*0.20 + c.g*0.60,&#039;,&lt;br /&gt;
        &#039;    l.b*0.30 + r.b*0.10 + c.b*0.60&#039;,&lt;br /&gt;
        &#039;  );&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyBloom(vec2 texUV, float strength) {&#039;,&lt;br /&gt;
        &#039;  vec2 px = 2.0 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 acc = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0,  px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0, -px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  return acc / 6.0 * strength;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyScanlines(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
        &#039;  float line = mod(uv.y * u_res.y, 2.0);&#039;,&lt;br /&gt;
        &#039;  vec3 hi = ((1.0 + 0.30) - 0.2 * col) * col;&#039;,&lt;br /&gt;
        &#039;  vec3 lo = ((1.0 - 0.30) + 0.1 * col) * col;&#039;,&lt;br /&gt;
        &#039;  return line &amp;lt; 1.0 ? lo : hi;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
&#039;vec3 applyRasterization(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
&#039;  float t = u_time;&#039;,&lt;br /&gt;
&#039;  vec2 noiseUV = uv + vec2(fract(t * 0.030), fract(t * 0.060));&#039;,&lt;br /&gt;
&#039;  float wobbleX = (texture2D(u_noise, noiseUV * 0.8).r - 0.5) * 0.0018;&#039;,&lt;br /&gt;
&#039;  float wobbleY = (texture2D(u_noise, noiseUV * 0.8 + 0.5).r - 0.5) * 0.0008;&#039;,&lt;br /&gt;
&#039;  vec2 wobbledUV = clamp(uv + vec2(wobbleX, wobbleY), 0.0, 1.0);&#039;,&lt;br /&gt;
&#039;  vec3 wobbled = texture2D(u_tex, wobbledUV).rgb;&#039;,&lt;br /&gt;
&#039;  return mix(col, wobbled, 0.35);&#039;,&lt;br /&gt;
&#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float glowingLine(vec2 uv, float t) {&#039;,&lt;br /&gt;
&#039;  float pos = fract(t * 0.2);&#039;,&lt;br /&gt;
&#039;  float lineY = pos * (u_res.y + 330.0) - 120.0;&#039;,&lt;br /&gt;
        &#039;  float y = uv.y * u_res.y;&#039;,&lt;br /&gt;
        &#039;  return fract(smoothstep(-300.0, 0.0, y - lineY));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 applyHSync(vec2 uv, vec4 noise, float strength) {&#039;,&lt;br /&gt;
        &#039;  float randval = strength - noise.r;&#039;,&lt;br /&gt;
        &#039;  float scale = step(0.0, randval) * randval * strength;&#039;,&lt;br /&gt;
        &#039;  float freq = mix(4.0, 40.0, noise.g);&#039;,&lt;br /&gt;
        &#039;  uv.x += sin((uv.y + u_time * 0.001) * freq) * scale;&#039;,&lt;br /&gt;
        &#039;  return uv;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 cc = vec2(0.5) - v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float curvature = 0.18;&#039;,&lt;br /&gt;
        &#039;  vec2 uv = barrel(v_uv, cc, curvature);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float inScreen = min2(step(vec2(0.0), uv) - step(vec2(1.0), uv));&#039;,&lt;br /&gt;
        &#039;  if (inScreen &amp;lt; 0.5) { gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); return; }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 texUV = clamp(coverUV(uv), 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec4 initNoise = sampleInitialNoise(u_time);&#039;,&lt;br /&gt;
        &#039;  vec4 screenNoise = sampleScreenNoise(uv);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV = applyHSync(texUV, initNoise, 0.006);&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV += (vec2(screenNoise.b, screenNoise.a) - 0.5) * 0.0006;&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec3 col = applyRgbShift(texUV, 0.003);&#039;,&lt;br /&gt;
        &#039;  col += applyBloom(texUV, 0.22);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 bpx = 1.5 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 blurCol = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,   -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,    bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  col = mix(col, blurCol / 8.0, 0.40);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = applyScanlines(uv, col);&#039;,&lt;br /&gt;
        &#039;  col = applyRasterization(texUV, col);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float glow = glowingLine(uv, u_time);&#039;,&lt;br /&gt;
&#039;  col += glow * 0.08 * vec3(0.85, 0.95, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float dist = length(cc);&#039;,&lt;br /&gt;
        &#039;  col += screenNoise.a * 0.07 * (1.0 - dist * 1.3);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float grey = rgb2grey(col);&#039;,&lt;br /&gt;
        &#039;  vec3 phosphor = vec3(0.75, 0.88, 1.0);&#039;,&lt;br /&gt;
        &#039;  col = mix(col, grey * phosphor, 0.35);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 vig = v_uv * (1.0 - v_uv);&#039;,&lt;br /&gt;
        &#039;  col *= pow(vig.x * vig.y * 15.0, 0.25);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col *= 1.0 + (initNoise.g - 0.5) * 0.06;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col += vec3(0.012) * (1.0 - dist) * (1.0 - dist);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = pow(clamp(col, 0.0, 1.0), vec3(0.90));&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  gl_FragColor = vec4(col, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function initCRTCanvas(screen, imgEl) {&lt;br /&gt;
        var existing = screen.querySelector(&#039;.crt-webgl-canvas&#039;);&lt;br /&gt;
        if (existing) existing.remove();&lt;br /&gt;
&lt;br /&gt;
        var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.className = &#039;crt-webgl-canvas&#039;;&lt;br /&gt;
        canvas.style.cssText = &#039;position:absolute;inset:0;width:100%;height:100%;z-index:19;pointer-events:none;display:block;&#039;;&lt;br /&gt;
        screen.appendChild(canvas);&lt;br /&gt;
&lt;br /&gt;
        var gl = canvas.getContext(&#039;webgl&#039;) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
        if (!gl) return;&lt;br /&gt;
&lt;br /&gt;
        function compile(type, src) {&lt;br /&gt;
            var s = gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(s, src);&lt;br /&gt;
            gl.compileShader(s);&lt;br /&gt;
            if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {&lt;br /&gt;
                console.error(&#039;[CRT shader]&#039;, gl.getShaderInfoLog(s));&lt;br /&gt;
            }&lt;br /&gt;
            return s;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var prog = gl.createProgram();&lt;br /&gt;
        gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT));&lt;br /&gt;
        gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FRAG));&lt;br /&gt;
        gl.linkProgram(prog);&lt;br /&gt;
        gl.useProgram(prog);&lt;br /&gt;
&lt;br /&gt;
        var buf = gl.createBuffer();&lt;br /&gt;
        gl.bindBuffer(gl.ARRAY_BUFFER, buf);&lt;br /&gt;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);&lt;br /&gt;
        var aPos = gl.getAttribLocation(prog, &#039;a_pos&#039;);&lt;br /&gt;
        gl.enableVertexAttribArray(aPos);&lt;br /&gt;
        gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
        var uTex     = gl.getUniformLocation(prog, &#039;u_tex&#039;);&lt;br /&gt;
        var uNoise   = gl.getUniformLocation(prog, &#039;u_noise&#039;);&lt;br /&gt;
        var uRes     = gl.getUniformLocation(prog, &#039;u_res&#039;);&lt;br /&gt;
        var uImgSize = gl.getUniformLocation(prog, &#039;u_imgSize&#039;);&lt;br /&gt;
        var uTime    = gl.getUniformLocation(prog, &#039;u_time&#039;);&lt;br /&gt;
        var uNoiseSc = gl.getUniformLocation(prog, &#039;u_noiseScale&#039;);&lt;br /&gt;
&lt;br /&gt;
        var imgTex = gl.createTexture();&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE1);&lt;br /&gt;
        createNoiseTexture(gl);&lt;br /&gt;
&lt;br /&gt;
        var texReady = false;&lt;br /&gt;
        function uploadImg() {&lt;br /&gt;
            if (!imgEl || !imgEl.complete || !imgEl.naturalWidth) return;&lt;br /&gt;
            try {&lt;br /&gt;
                gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
                gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imgEl);&lt;br /&gt;
                texReady = true;&lt;br /&gt;
            } catch(e) { console.error(&#039;[CRT] tex:&#039;, e); }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var lastW = 0, lastH = 0;&lt;br /&gt;
        function resize() {&lt;br /&gt;
            var w = screen.offsetWidth, h = screen.offsetHeight;&lt;br /&gt;
            if (w === lastW &amp;amp;&amp;amp; h === lastH) return;&lt;br /&gt;
            lastW = w; lastH = h;&lt;br /&gt;
            canvas.width = w; canvas.height = h;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var raf;&lt;br /&gt;
        var t0 = performance.now();&lt;br /&gt;
&lt;br /&gt;
        function render() {&lt;br /&gt;
            raf = requestAnimationFrame(render);&lt;br /&gt;
            if (!texReady) { uploadImg(); return; }&lt;br /&gt;
            resize();&lt;br /&gt;
            var t = (performance.now() - t0) / 1000;&lt;br /&gt;
            gl.uniform1i(uTex, 0);&lt;br /&gt;
            gl.uniform1i(uNoise, 1);&lt;br /&gt;
            gl.uniform2f(uRes, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform2f(uImgSize, imgEl.naturalWidth, imgEl.naturalHeight);&lt;br /&gt;
            gl.uniform1f(uTime, t);&lt;br /&gt;
            gl.uniform2f(uNoiseSc, canvas.width / 512.0, canvas.height / 512.0);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) { uploadImg(); }&lt;br /&gt;
        else { imgEl.addEventListener(&#039;load&#039;, uploadImg); }&lt;br /&gt;
&lt;br /&gt;
        render();&lt;br /&gt;
        screen._crtCleanup = function () { cancelAnimationFrame(raf); };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initAllCRTScreens(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        scope.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (screen) {&lt;br /&gt;
            if (screen.getAttribute(&#039;data-crt-webgl&#039;) === &#039;1&#039;) return;&lt;br /&gt;
            screen.setAttribute(&#039;data-crt-webgl&#039;, &#039;1&#039;);&lt;br /&gt;
            var frame = screen.closest(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
            if (!frame) return;&lt;br /&gt;
            var imgEl = frame.querySelector(&#039;.crt-page-monitor-slice-img, .crt-page-monitor-image-base img&#039;);&lt;br /&gt;
            if (imgEl &amp;amp;&amp;amp; imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) {&lt;br /&gt;
                initCRTCanvas(screen, imgEl);&lt;br /&gt;
            } else if (imgEl) {&lt;br /&gt;
                imgEl.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, imgEl); });&lt;br /&gt;
            } else {&lt;br /&gt;
                var obs = new MutationObserver(function () {&lt;br /&gt;
                    var img = frame.querySelector(&#039;.crt-page-monitor-slice-img&#039;);&lt;br /&gt;
                    if (!img) return;&lt;br /&gt;
                    obs.disconnect();&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        initCRTCanvas(screen, img);&lt;br /&gt;
                    } else {&lt;br /&gt;
                        img.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, img); });&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
                obs.observe(frame, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () { initAllCRTScreens(document); });&lt;br /&gt;
&lt;br /&gt;
    if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($c) {&lt;br /&gt;
            document.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (s) {&lt;br /&gt;
                if (s._crtCleanup) s._crtCleanup();&lt;br /&gt;
                s.removeAttribute(&#039;data-crt-webgl&#039;);&lt;br /&gt;
            });&lt;br /&gt;
            initAllCRTScreens($c &amp;amp;&amp;amp; $c[0] ? $c[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Progress System UI&lt;br /&gt;
MediaWiki:Common.js controlled frontend&lt;br /&gt;
========================================= */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUiInitialized) return;&lt;br /&gt;
    window.ProgressSystemWebUiInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    var api = null;&lt;br /&gt;
&lt;br /&gt;
    function withApi(done, fail) {&lt;br /&gt;
        if (api) {&lt;br /&gt;
            done(api);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
            api = new mw.Api();&lt;br /&gt;
            done(api);&lt;br /&gt;
        }, function () {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var inFlightPageIds = new Set();&lt;br /&gt;
    var handledPageIds = new Set();&lt;br /&gt;
    var notificationQueue = [];&lt;br /&gt;
    var notificationActive = false;&lt;br /&gt;
    var summaryRequested = false;&lt;br /&gt;
    var currentSummary = null;&lt;br /&gt;
    var pendingSummary = null;&lt;br /&gt;
    var pendingOptions = null;&lt;br /&gt;
    var visibilityBound = false;&lt;br /&gt;
    var barTimerA = null;&lt;br /&gt;
    var barTimerB = null;&lt;br /&gt;
    var barTimerC = null;&lt;br /&gt;
    var summaryRetryTimer = null;&lt;br /&gt;
    var summaryRetryAttempts = 0;&lt;br /&gt;
&lt;br /&gt;
    function isLoggedIn() {&lt;br /&gt;
        return !!mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPageId() {&lt;br /&gt;
        var id = parseInt(mw.config.get(&#039;wgArticleId&#039;) || 0, 10);&lt;br /&gt;
        return Number.isFinite(id) ? id : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isRewardableClientSide() {&lt;br /&gt;
        if (!isLoggedIn()) return false;&lt;br /&gt;
        if (parseInt(mw.config.get(&#039;wgNamespaceNumber&#039;), 10) !== 0) return false;&lt;br /&gt;
        if (mw.config.get(&#039;wgIsMainPage&#039;)) return false;&lt;br /&gt;
        if (getPageId() &amp;lt;= 0) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPanelHtml() {&lt;br /&gt;
        return &#039;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;progress-panel&amp;quot; class=&amp;quot;profile-progress-block is-syncing&amp;quot; aria-live=&amp;quot;polite&amp;quot; data-progress-state=&amp;quot;syncing&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-title-row&amp;quot; hidden&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-level-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-level-label&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-gain&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-sub-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-xp-next&amp;quot;&amp;gt;SYNCING&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-daily-xp&amp;quot;&amp;gt;TODAY —&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-discovery-row&amp;quot;&amp;gt;DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDividerHtml() {&lt;br /&gt;
        /* 프로필 패널 최신 규칙: 레벨 패널과 버튼 영역 사이에 별도 나눔선은 만들지 않는다. */&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setPanelSync($panel) {&lt;br /&gt;
        if (!$panel || !$panel.length) return;&lt;br /&gt;
&lt;br /&gt;
        $panel.addClass(&#039;is-syncing&#039;).removeClass(&#039;is-max-level&#039;).attr(&#039;data-progress-state&#039;, &#039;syncing&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-title-row&#039;).text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text(&#039;SYNC&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(&#039;— XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(&#039;SYNCING&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-fill&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
        $panel.find(&#039;.progress-xp-gain&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function placePanel($panel) {&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $userBox = $right.children(&#039;.clbi-right-box&#039;).first();&lt;br /&gt;
        if (!$userBox.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $buttonArea = $userBox.children(&#039;.clbi-right-content&#039;).first();&lt;br /&gt;
        var $oldFallback = $panel.closest(&#039;.progress-panel-fallback&#039;);&lt;br /&gt;
&lt;br /&gt;
        if ($buttonArea.length) {&lt;br /&gt;
            var $divider = $(&#039;#profile-progress-divider&#039;);&lt;br /&gt;
&lt;br /&gt;
            $panel.insertBefore($buttonArea);&lt;br /&gt;
&lt;br /&gt;
            if (!$divider.length) {&lt;br /&gt;
                $divider = $(getDividerHtml());&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $divider.insertAfter($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;#profile-progress-divider&#039;).remove();&lt;br /&gt;
            $userBox.append($panel);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if ($oldFallback.length &amp;amp;&amp;amp; !$oldFallback.find(&#039;#progress-panel&#039;).length) {&lt;br /&gt;
            $oldFallback.remove();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensurePanel() {&lt;br /&gt;
        if (!isLoggedIn()) return $();&lt;br /&gt;
&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return $();&lt;br /&gt;
&lt;br /&gt;
        var $panel = $(&#039;#progress-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            $panel = $(getPanelHtml());&lt;br /&gt;
            if (!placePanel($panel)) return $();&lt;br /&gt;
            setPanelSync($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $panel.addClass(&#039;profile-progress-block&#039;);&lt;br /&gt;
            placePanel($panel);&lt;br /&gt;
&lt;br /&gt;
            if (!currentSummary &amp;amp;&amp;amp; $panel.attr(&#039;data-progress-state&#039;) !== &#039;syncing&#039;) {&lt;br /&gt;
                setPanelSync($panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $(&#039;#progress-panel&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clampPercent(value) {&lt;br /&gt;
        return Math.max(0, Math.min(100, value || 0));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasXpNotification(items) {&lt;br /&gt;
        if (!items || !items.length) return false;&lt;br /&gt;
        return items.some(function (item) {&lt;br /&gt;
            return item &amp;amp;&amp;amp; item.type === &#039;xp&#039; &amp;amp;&amp;amp; parseInt(item.amount || 0, 10) &amp;gt; 0;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearBarTimers() {&lt;br /&gt;
        [barTimerA, barTimerB, barTimerC].forEach(function (timer) {&lt;br /&gt;
            if (timer) clearTimeout(timer);&lt;br /&gt;
        });&lt;br /&gt;
        barTimerA = null;&lt;br /&gt;
        barTimerB = null;&lt;br /&gt;
        barTimerC = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBarInstant($fill, $gain, percent) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
        percent = clampPercent(percent);&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: percent + &#039;%&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;none&#039;, left: &#039;0%&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
        $fill.css({ transition: &#039;&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function animateGain($fill, $gain, fromPercent, toPercent, levelChanged) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
&lt;br /&gt;
        fromPercent = clampPercent(fromPercent);&lt;br /&gt;
        toPercent = clampPercent(toPercent);&lt;br /&gt;
&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: fromPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
        if (levelChanged) {&lt;br /&gt;
            var firstDelta = Math.max(0, 100 - fromPercent);&lt;br /&gt;
&lt;br /&gt;
            $gain.css({&lt;br /&gt;
                transition: &#039;none&#039;,&lt;br /&gt;
                opacity: firstDelta &amp;gt; 0 ? 1 : 0,&lt;br /&gt;
                left: fromPercent + &#039;%&#039;,&lt;br /&gt;
                width: firstDelta + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
            barTimerA = setTimeout(function () {&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 540ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: &#039;100%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 260);&lt;br /&gt;
&lt;br /&gt;
            barTimerB = setTimeout(function () {&lt;br /&gt;
                $fill.css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
                $gain.css({ transition: &#039;none&#039;, opacity: toPercent &amp;gt; 0 ? 1 : 0, left: &#039;0%&#039;, width: toPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
                if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 460ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: toPercent + &#039;%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 860);&lt;br /&gt;
&lt;br /&gt;
            barTimerC = setTimeout(function () {&lt;br /&gt;
                $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
            }, 1380);&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var delta = Math.max(0, toPercent - fromPercent);&lt;br /&gt;
&lt;br /&gt;
        if (delta &amp;lt;= 0.15) {&lt;br /&gt;
            setBarInstant($fill, $gain, toPercent);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $gain.css({&lt;br /&gt;
            transition: &#039;none&#039;,&lt;br /&gt;
            opacity: 1,&lt;br /&gt;
            left: fromPercent + &#039;%&#039;,&lt;br /&gt;
            width: delta + &#039;%&#039;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
        barTimerA = setTimeout(function () {&lt;br /&gt;
            $fill.css({&lt;br /&gt;
                transition: &#039;width 560ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                width: toPercent + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }, 260);&lt;br /&gt;
&lt;br /&gt;
        barTimerB = setTimeout(function () {&lt;br /&gt;
            $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
        }, 940);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updatePanel(summary, options) {&lt;br /&gt;
        if (!summary) return;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
&lt;br /&gt;
        var $panel = ensurePanel();&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            pendingSummary = $.extend({}, summary);&lt;br /&gt;
            pendingOptions = $.extend({}, options);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var level = summary.level || 1;&lt;br /&gt;
        var totalXp = summary.totalXp || 0;&lt;br /&gt;
        var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
        var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
        var percent = clampPercent(summary.progressPercent);&lt;br /&gt;
        var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
        var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
        var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
        var title = summary.equippedTitle || summary.title || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $panel.removeClass(&#039;is-syncing&#039;).toggleClass(&#039;is-max-level&#039;, isMaxLevel).attr(&#039;data-progress-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text((isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(totalXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;));&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY &#039; + dailyXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED &#039; + discoveries);&lt;br /&gt;
&lt;br /&gt;
        var $title = $panel.find(&#039;.progress-title-row&#039;);&lt;br /&gt;
        if (title) {&lt;br /&gt;
            $title.text(title).prop(&#039;hidden&#039;, false);&lt;br /&gt;
        } else {&lt;br /&gt;
            $title.text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $fill = $panel.find(&#039;.progress-xp-fill&#039;);&lt;br /&gt;
        var $gain = $panel.find(&#039;.progress-xp-gain&#039;);&lt;br /&gt;
        var animate = !!options.animateGain &amp;amp;&amp;amp; currentSummary &amp;amp;&amp;amp; totalXp &amp;gt; (currentSummary.totalXp || 0);&lt;br /&gt;
&lt;br /&gt;
        if (animate) {&lt;br /&gt;
            animateGain(&lt;br /&gt;
                $fill,&lt;br /&gt;
                $gain,&lt;br /&gt;
                clampPercent(currentSummary.progressPercent),&lt;br /&gt;
                percent,&lt;br /&gt;
                level !== (currentSummary.level || 1)&lt;br /&gt;
            );&lt;br /&gt;
        } else {&lt;br /&gt;
            setBarInstant($fill, $gain, percent);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        currentSummary = $.extend({}, summary);&lt;br /&gt;
        pendingSummary = null;&lt;br /&gt;
        pendingOptions = null;&lt;br /&gt;
        if (summaryRetryTimer) {&lt;br /&gt;
            clearTimeout(summaryRetryTimer);&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
        }&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearSummaryRetry() {&lt;br /&gt;
        if (summaryRetryTimer) clearTimeout(summaryRetryTimer);&lt;br /&gt;
        summaryRetryTimer = null;&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleSummaryRetry(delay) {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRetryTimer) return;&lt;br /&gt;
        if (summaryRetryAttempts &amp;gt;= 12) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRetryAttempts += 1;&lt;br /&gt;
        summaryRetryTimer = setTimeout(function () {&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, delay || 1800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSummary() {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRequested) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRequested = true;&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;progress_summary&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
                if (payload &amp;amp;&amp;amp; payload.available &amp;amp;&amp;amp; payload.summary) {&lt;br /&gt;
                    clearSummaryRetry();&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: false });&lt;br /&gt;
                } else {&lt;br /&gt;
                    scheduleSummaryRetry(2200);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                scheduleSummaryRetry(2200);&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                summaryRequested = false;&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            summaryRequested = false;&lt;br /&gt;
            scheduleSummaryRetry(2200);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queueNotifications(items) {&lt;br /&gt;
        if (!items || !items.length) return;&lt;br /&gt;
&lt;br /&gt;
        items.forEach(function (item) {&lt;br /&gt;
            if (!item) return;&lt;br /&gt;
            notificationQueue.push(item);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        showNextNotification();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function notificationText(item) {&lt;br /&gt;
        if (item.type === &#039;xp&#039;) {&lt;br /&gt;
            return &#039;+&#039; + (item.amount || 0) + &#039; XP · &#039; + (item.label || &#039;문서 열람&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;achievement&#039;) {&lt;br /&gt;
            var xp = item.amount ? &#039; · +&#039; + item.amount + &#039; XP&#039; : &#039;&#039;;&lt;br /&gt;
            return &#039;업적 달성 · &#039; + (item.label || &#039;새 업적&#039;) + xp;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;level&#039;) {&lt;br /&gt;
            return item.label || &#039;레벨 상승&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return item.label || &#039;보상 획득&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showNextNotification() {&lt;br /&gt;
        if (notificationActive) return;&lt;br /&gt;
        if (!notificationQueue.length) return;&lt;br /&gt;
&lt;br /&gt;
        notificationActive = true;&lt;br /&gt;
        var item = notificationQueue.shift();&lt;br /&gt;
        var $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$root.length) {&lt;br /&gt;
            $(&#039;body&#039;).append(&#039;&amp;lt;div id=&amp;quot;progress-toast-root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $toast = $(&#039;&amp;lt;div class=&amp;quot;progress-toast&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        $toast.text(notificationText(item));&lt;br /&gt;
        $root.append($toast);&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            $toast.addClass(&#039;is-visible&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            $toast.removeClass(&#039;is-visible&#039;);&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                $toast.remove();&lt;br /&gt;
                notificationActive = false;&lt;br /&gt;
                showNextNotification();&lt;br /&gt;
            }, 220);&lt;br /&gt;
        }, 2600);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPendingSummaryIfPossible() {&lt;br /&gt;
        if (!pendingSummary) return;&lt;br /&gt;
        updatePanel(pendingSummary, pendingOptions || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handlePageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (!isRewardableClientSide()) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var pageId = getPageId();&lt;br /&gt;
        if (handledPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (inFlightPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inFlightPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;progress_view&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2,&lt;br /&gt;
                errorformat: &#039;plaintext&#039;,&lt;br /&gt;
                pageid: pageId&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_view;&lt;br /&gt;
                if (!payload) return;&lt;br /&gt;
&lt;br /&gt;
                handledPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
                var animate = hasXpNotification(payload.notifications);&lt;br /&gt;
&lt;br /&gt;
                if (payload.summary) {&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: animate });&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (payload.notifications &amp;amp;&amp;amp; payload.notifications.length) {&lt;br /&gt;
                    queueNotifications(payload.notifications);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                inFlightPageIds.delete(pageId);&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            inFlightPageIds.delete(pageId);&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindVisibilitySync() {&lt;br /&gt;
        if (visibilityBound) return;&lt;br /&gt;
        visibilityBound = true;&lt;br /&gt;
&lt;br /&gt;
        document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
            if (document.visibilityState === &#039;visible&#039;) {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bootProgressSystem(reason) {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (isRewardableClientSide()) {&lt;br /&gt;
            handlePageView();&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 350);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 1500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleSpaPageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                handlePageView();&lt;br /&gt;
            }, 80);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySummary(summary, options) {&lt;br /&gt;
        updatePanel(summary, options || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.ProgressSystemWebUi = {&lt;br /&gt;
        boot: bootProgressSystem,&lt;br /&gt;
        requestSummary: requestSummary,&lt;br /&gt;
        applySummary: applySummary,&lt;br /&gt;
        handlePageView: handlePageView,&lt;br /&gt;
        handleSpaPageView: handleSpaPageView,&lt;br /&gt;
        ensurePanel: ensurePanel&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        bindVisibilitySync();&lt;br /&gt;
        bootProgressSystem(&#039;documentReady&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* CLBI Nations / Historical Events year tabs&lt;br /&gt;
 * Mirrors the country information panel model:&lt;br /&gt;
 * active tab uses .is-active/aria-selected and inactive pages use hidden.&lt;br /&gt;
 */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function activateClbiNationsHistoryYear(panel, targetYear) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var pages;&lt;br /&gt;
&lt;br /&gt;
        if (!panel || !targetYear) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        pages = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-page[data-year-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
        if (!tabs.length || !pages.length) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (tab) {&lt;br /&gt;
            var active = tab.getAttribute(&#039;data-year&#039;) === targetYear;&lt;br /&gt;
            tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
            tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        pages.forEach(function (page) {&lt;br /&gt;
            var active = page.getAttribute(&#039;data-year-panel&#039;) === targetYear;&lt;br /&gt;
            page.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
            if (active) {&lt;br /&gt;
                page.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
            } else {&lt;br /&gt;
                page.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveClbiNationsHistoryYear(panel, direction) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var activeIndex;&lt;br /&gt;
        var nextIndex;&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!panel) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
        activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
            return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
        nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
        target = tabs[nextIndex].getAttribute(&#039;data-year&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (activateClbiNationsHistoryYear(panel, target)) {&lt;br /&gt;
            tabs[nextIndex].focus();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initClbiNationsHistoryYearTabs(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var panels = scope.querySelectorAll(&#039;.clbi-nations-history-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(panels, function (panel) {&lt;br /&gt;
            if (panel.getAttribute(&#039;data-clbi-history-tabs-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
            panel.setAttribute(&#039;data-clbi-history-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (activateClbiNationsHistoryYear(panel, tab.getAttribute(&#039;data-year&#039;))) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
                var handled = false;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsHistoryYear(panel, -1);&lt;br /&gt;
                else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsHistoryYear(panel, 1);&lt;br /&gt;
                else if (event.key === &#039;Home&#039;) handled = activateClbiNationsHistoryYear(panel, (panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;) || {}).getAttribute &amp;amp;&amp;amp; panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;).getAttribute(&#039;data-year&#039;));&lt;br /&gt;
                else if (event.key === &#039;End&#039;) {&lt;br /&gt;
                    var tabs = panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;);&lt;br /&gt;
                    var last = tabs[tabs.length - 1];&lt;br /&gt;
                    handled = last ? activateClbiNationsHistoryYear(panel, last.getAttribute(&#039;data-year&#039;)) : false;&lt;br /&gt;
                    if (handled) last.focus();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (handled) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                    event.stopPropagation();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.initClbiNationsHistoryYearTabs = initClbiNationsHistoryYearTabs;&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initClbiNationsHistoryYearTabs(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initClbiNationsHistoryYearTabs($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Decoration runtime renderer&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var REGISTRY_TITLE = &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
    var RENDERED_ATTR = &#039;data-wiki-decoration-rendered&#039;;&lt;br /&gt;
    var HOST_ATTR = &#039;data-wiki-decoration-host&#039;;&lt;br /&gt;
    var MAIN_PAGE_PLACEMENT = &#039;main-body-well&#039;;&lt;br /&gt;
    var MAIN_PAGE_TARGET = &#039;.main-portal .main-body-well&#039;;&lt;br /&gt;
    var runtimeToken = 0;&lt;br /&gt;
    var lastRegistry = null;&lt;br /&gt;
    var lastRenderedPageKey = &#039;&#039;;&lt;br /&gt;
    var scheduledRender = 0;&lt;br /&gt;
    var nationsPlacementObserver = null;&lt;br /&gt;
    var observedNationsStack = null;&lt;br /&gt;
    var mainPagePlacementObserver = null;&lt;br /&gt;
    var mainPagePlacementMutationObserver = null;&lt;br /&gt;
    var observedMainPagePortal = null;&lt;br /&gt;
    var scheduledMainPageRebase = 0;&lt;br /&gt;
    var pixelAssetCache = {};&lt;br /&gt;
    var pixelCanvasCache = {};&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentPageKey() {&lt;br /&gt;
        var raw = mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        return normalizePageName(raw) || raw || &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureMainPageBodyWell() {&lt;br /&gt;
        var portal;&lt;br /&gt;
        var topMount;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var well;&lt;br /&gt;
        var manifesto;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
        portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        if (!portal) return null;&lt;br /&gt;
&lt;br /&gt;
        topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) {&lt;br /&gt;
                    topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                } else {&lt;br /&gt;
                    topMount.parentNode.appendChild(panel);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) {&lt;br /&gt;
            well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return well;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function cssAttrEscape(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).replace(/\\/g, &#039;\\\\&#039;).replace(/&amp;quot;/g, &#039;\\&amp;quot;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEra() {&lt;br /&gt;
        var content = document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var globe;&lt;br /&gt;
&lt;br /&gt;
        if (content) return content.getAttribute(&#039;data-era-content&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.querySelector(&#039;.clbi-nations-era-title-plate.is-active[data-era]&#039;);&lt;br /&gt;
        if (title) return title.getAttribute(&#039;data-era&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe]&#039;);&lt;br /&gt;
        if (globe) {&lt;br /&gt;
            return globe.getAttribute(&#039;data-current-era&#039;) || globe.getAttribute(&#039;data-nations-current-era&#039;) || globe.getAttribute(&#039;data-era-year&#039;) || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEraPanel() {&lt;br /&gt;
        var era = getActiveNationsEra();&lt;br /&gt;
        var selector;&lt;br /&gt;
        if (!era) return document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        selector = &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;]&#039;;&lt;br /&gt;
        return document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsContinent() {&lt;br /&gt;
        var eraPanel = getActiveNationsEraPanel();&lt;br /&gt;
        var root = eraPanel || document;&lt;br /&gt;
        var tab = root.querySelector(&#039;.clbi-nations-tabpanel-tab.is-active[data-continent], .clbi-nations-tabpanel-tab[aria-selected=&amp;quot;true&amp;quot;][data-continent]&#039;);&lt;br /&gt;
        var panel;&lt;br /&gt;
&lt;br /&gt;
        if (tab) return tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        panel = root.querySelector(&#039;.clbi-nations-tabpanel-continent.is-active[data-continent-panel]&#039;);&lt;br /&gt;
        if (panel) return panel.getAttribute(&#039;data-continent-panel&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationNationsBodySelector(era) {&lt;br /&gt;
        if (era) {&lt;br /&gt;
            return &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;] .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden]) .clbi-nations-tabpanel-body, .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Decoration semantic placement resolver&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    장식 저장 데이터의 placement 값은 &amp;quot;사용자가 고른 의미상 위치&amp;quot;를 나타낸다.&lt;br /&gt;
    resolver는 그 의미값을 실제 DOM 부착 위치와 표시 조건으로 번역한다.&lt;br /&gt;
&lt;br /&gt;
    예: 시대 문서에서 사용자가 1950년 / 아메리카를 지정하면 의미상 scope는&lt;br /&gt;
    그 조합이지만, 이미지를 붙일 기준면은 대륙 패널 자체가 아니라&lt;br /&gt;
    .clbi-nations-tabpanel-body이다. 따라서 placement=nations-continent-body는&lt;br /&gt;
    .clbi-nations-tabpanel-body에 이미지를 붙이고, 현재 활성 연도와 대륙이 저장값과&lt;br /&gt;
    일치할 때만 렌더링한다.&lt;br /&gt;
&lt;br /&gt;
    유지보수 규칙:&lt;br /&gt;
    - 새 조합형 문서가 생기면 entry.target을 매번 특수하게 저장하지 말고 placement를 추가한다.&lt;br /&gt;
    - DevTools.js 에디터 미리보기와 Common.js 런타임 렌더러의 resolver는 같은 의미를 가져야 한다.&lt;br /&gt;
    - target은 물리적 기준면, era/continent 같은 필드는 표시 조건으로 다룬다.&lt;br /&gt;
    */&lt;br /&gt;
    function shouldUseNationsBodyPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var target = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!document.querySelector(&#039;.clbi-nations-panel-stack&#039;)) return false;&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) return true;&lt;br /&gt;
        if (era || continent) return true;&lt;br /&gt;
        if (target.indexOf(&#039;clbi-nations-tabpanel-continent&#039;) !== -1) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveDecorationPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() || &#039;.liberty-content-main&#039;;&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!placement &amp;amp;&amp;amp; shouldUseNationsBodyPlacement(entry)) {&lt;br /&gt;
            placement = &#039;nations-continent-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;boot-gate&#039; || placement === &#039;loading-screen&#039;) {&lt;br /&gt;
            selector = &#039;#boot-gate-screen .boot-gate-decoration-layer, #boot-gate-screen&#039;;&lt;br /&gt;
            target = document.querySelector(selector);&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: !!document.getElementById(&#039;boot-gate-screen&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) {&lt;br /&gt;
            selector = getDecorationNationsBodySelector(era);&lt;br /&gt;
            target = document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-tabpanel-body&#039;) || document.querySelector(String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim());&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: (!era || era === getActiveNationsEra()) &amp;amp;&amp;amp; (!continent || continent === getActiveNationsContinent())&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문의 일반 장식은 저장된 target과 관계없이 본문 우물 안에서 렌더링한다.&lt;br /&gt;
        우물이 overflow:hidden이므로 #1d1d1d 프레임 밖으로 나갈 수 없다.&lt;br /&gt;
        boot/loading 및 국가 패널 전용 placement는 위 분기에서 기존 동작을 유지한다.&lt;br /&gt;
        */&lt;br /&gt;
        var mainPageWell = ensureMainPageBodyWell();&lt;br /&gt;
        if (mainPageWell) {&lt;br /&gt;
            return {&lt;br /&gt;
                placement: MAIN_PAGE_PLACEMENT,&lt;br /&gt;
                target: mainPageWell,&lt;br /&gt;
                targetSelector: MAIN_PAGE_TARGET,&lt;br /&gt;
                visible: true&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            placement: placement,&lt;br /&gt;
            target: document.querySelector(selector),&lt;br /&gt;
            targetSelector: selector,&lt;br /&gt;
            visible: true&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeNumber(value, fallback) {&lt;br /&gt;
        var n = parseFloat(value);&lt;br /&gt;
        return Number.isFinite(n) ? n : fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBool(value, fallback) {&lt;br /&gt;
        if (value === true || value === &#039;true&#039; || value === &#039;1&#039; || value === 1) return true;&lt;br /&gt;
        if (value === false || value === &#039;false&#039; || value === &#039;0&#039; || value === 0) return false;&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeSrc(src) {&lt;br /&gt;
        src = String(src || &#039;&#039;).trim();&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(src) || src.charAt(0) === &#039;/&#039; || src.indexOf(&#039;data:&#039;) === 0 || src.indexOf(&#039;blob:&#039;) === 0) return src;&lt;br /&gt;
        return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(src);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function normalizeAssetType(entry) {&lt;br /&gt;
        var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var ref = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (type === &#039;clbi-pixel-json&#039; || type === &#039;pixel-rle&#039; || type === &#039;pixel-json&#039;) return &#039;pixel-json&#039;;&lt;br /&gt;
        if (!type &amp;amp;&amp;amp; /\.json(?:[?#].*)?$/i.test(ref)) return &#039;pixel-json&#039;;&lt;br /&gt;
        return type || &#039;image&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPixelJsonDecoration(entry) {&lt;br /&gt;
        return normalizeAssetType(entry) === &#039;pixel-json&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPixelJsonRef(entry) {&lt;br /&gt;
        return String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelJsonUrl(ref) {&lt;br /&gt;
        ref = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        if (!ref) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(ref) || ref.charAt(0) === &#039;/&#039; || ref.indexOf(&#039;data:&#039;) === 0 || ref.indexOf(&#039;blob:&#039;) === 0) return ref;&lt;br /&gt;
        if (/^(?:file|파일):/i.test(ref)) return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(ref.replace(/^(?:file|파일):/i, &#039;&#039;));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            if (ref.indexOf(&#039;:&#039;) !== -1) {&lt;br /&gt;
                return mw.util.getUrl(ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
            }&lt;br /&gt;
            return mw.util.getUrl(&#039;MediaWiki:&#039; + ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return ref;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function parsePixelColor(value) {&lt;br /&gt;
        var text;&lt;br /&gt;
        var m;&lt;br /&gt;
        if (Array.isArray(value)) {&lt;br /&gt;
            return [&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[0]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[1]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[2]) || 0)),&lt;br /&gt;
                value.length &amp;gt; 3 ? Math.max(0, Math.min(255, Number(value[3]) || 0)) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        m = text.match(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i);&lt;br /&gt;
        if (!m) return [0, 0, 0, 0];&lt;br /&gt;
        text = m[1];&lt;br /&gt;
        if (text.length === 3 || text.length === 4) {&lt;br /&gt;
            return [&lt;br /&gt;
                parseInt(text.charAt(0) + text.charAt(0), 16),&lt;br /&gt;
                parseInt(text.charAt(1) + text.charAt(1), 16),&lt;br /&gt;
                parseInt(text.charAt(2) + text.charAt(2), 16),&lt;br /&gt;
                text.length === 4 ? parseInt(text.charAt(3) + text.charAt(3), 16) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        return [&lt;br /&gt;
            parseInt(text.slice(0, 2), 16),&lt;br /&gt;
            parseInt(text.slice(2, 4), 16),&lt;br /&gt;
            parseInt(text.slice(4, 6), 16),&lt;br /&gt;
            text.length === 8 ? parseInt(text.slice(6, 8), 16) : 255&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function decodePixelRle36(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var parts;&lt;br /&gt;
        var runs = [];&lt;br /&gt;
        var i;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
&lt;br /&gt;
        if (!text) return runs;&lt;br /&gt;
        parts = text.split(&#039;,&#039;);&lt;br /&gt;
        for (i = 0; i + 3 &amp;lt; parts.length; i += 4) {&lt;br /&gt;
            x = parseInt(parts[i], 36);&lt;br /&gt;
            y = parseInt(parts[i + 1], 36);&lt;br /&gt;
            len = parseInt(parts[i + 2], 36);&lt;br /&gt;
            colorIndex = parseInt(parts[i + 3], 36);&lt;br /&gt;
            if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(len) || !Number.isFinite(colorIndex)) continue;&lt;br /&gt;
            runs.push([x, y, len, colorIndex]);&lt;br /&gt;
        }&lt;br /&gt;
        return runs;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelAsset(doc) {&lt;br /&gt;
        var encoding = String(doc &amp;amp;&amp;amp; (doc.encoding || doc.e) || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var width = Math.round(Number(doc &amp;amp;&amp;amp; (doc.width || doc.w)) || 0);&lt;br /&gt;
        var height = Math.round(Number(doc &amp;amp;&amp;amp; (doc.height || doc.h)) || 0);&lt;br /&gt;
        var paletteSource = Array.isArray(doc &amp;amp;&amp;amp; doc.palette) ? doc.palette : (Array.isArray(doc &amp;amp;&amp;amp; doc.p) ? doc.p : []);&lt;br /&gt;
        var palette = paletteSource.map(parsePixelColor);&lt;br /&gt;
        var runs;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        CLBI Pixel Forge v0.2.3 compact format:&lt;br /&gt;
        - MediaWiki 단일 문서 크기 제한을 피하기 위해 사람이 읽기 쉬운 [[x,y,len,c], ...] 배열 대신&lt;br /&gt;
          base36 토큰 문자열을 쓴다.&lt;br /&gt;
        - 형식은 x,y,len,colorIndex를 4개 토큰 단위로 반복한 rle36이다.&lt;br /&gt;
        - 복원 후 좌표계는 기존 runs 배열과 완전히 같으며, 최종 CSS px에 1:1로 찍는다.&lt;br /&gt;
        */&lt;br /&gt;
        if ((encoding === &#039;rle36&#039; || encoding === &#039;clbi-rle36&#039;) &amp;amp;&amp;amp; typeof (doc &amp;amp;&amp;amp; doc.r) === &#039;string&#039;) {&lt;br /&gt;
            runs = decodePixelRle36(doc.r);&lt;br /&gt;
        } else {&lt;br /&gt;
            runs = Array.isArray(doc &amp;amp;&amp;amp; doc.runs) ? doc.runs : [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!width || !height || width &amp;lt; 1 || height &amp;lt; 1) throw new Error(&#039;invalid pixel decoration size&#039;);&lt;br /&gt;
        if (width &amp;gt; 8192 || height &amp;gt; 8192) throw new Error(&#039;pixel decoration too large&#039;);&lt;br /&gt;
        return {&lt;br /&gt;
            type: String(doc &amp;amp;&amp;amp; (doc.type || doc.t) || &#039;clbi-pixel-decoration&#039;),&lt;br /&gt;
            version: Number(doc &amp;amp;&amp;amp; (doc.version || doc.v)) || 1,&lt;br /&gt;
            encoding: encoding || &#039;runs&#039;,&lt;br /&gt;
            width: width,&lt;br /&gt;
            height: height,&lt;br /&gt;
            palette: palette,&lt;br /&gt;
            runs: runs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchPixelAsset(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelAssetCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {&lt;br /&gt;
            promise: fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
                .then(function (response) {&lt;br /&gt;
                    if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                    return response.json();&lt;br /&gt;
                })&lt;br /&gt;
                .then(normalizePixelAsset)&lt;br /&gt;
        };&lt;br /&gt;
        pixelAssetCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawPixelAssetToCanvas(canvas, asset) {&lt;br /&gt;
        var ctx;&lt;br /&gt;
        var image;&lt;br /&gt;
        var data;&lt;br /&gt;
        var i;&lt;br /&gt;
        var run;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var color;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
        var p;&lt;br /&gt;
        var k;&lt;br /&gt;
&lt;br /&gt;
        canvas.width = asset.width;&lt;br /&gt;
        canvas.height = asset.height;&lt;br /&gt;
        ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        ctx.imageSmoothingEnabled = false;&lt;br /&gt;
        image = ctx.createImageData(asset.width, asset.height);&lt;br /&gt;
        data = image.data;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; asset.runs.length; i += 1) {&lt;br /&gt;
            run = asset.runs[i];&lt;br /&gt;
            if (!Array.isArray(run) || run.length &amp;lt; 4) continue;&lt;br /&gt;
            x = Math.round(Number(run[0]) || 0);&lt;br /&gt;
            y = Math.round(Number(run[1]) || 0);&lt;br /&gt;
            len = Math.round(Number(run[2]) || 0);&lt;br /&gt;
            colorIndex = Math.round(Number(run[3]) || 0);&lt;br /&gt;
            color = asset.palette[colorIndex];&lt;br /&gt;
            if (!color || len &amp;lt;= 0 || y &amp;lt; 0 || y &amp;gt;= asset.height || x &amp;gt;= asset.width) continue;&lt;br /&gt;
            if (x &amp;lt; 0) {&lt;br /&gt;
                len += x;&lt;br /&gt;
                x = 0;&lt;br /&gt;
            }&lt;br /&gt;
            len = Math.min(len, asset.width - x);&lt;br /&gt;
            for (k = 0; k &amp;lt; len; k += 1) {&lt;br /&gt;
                p = ((y * asset.width) + x + k) * 4;&lt;br /&gt;
                data[p] = color[0];&lt;br /&gt;
                data[p + 1] = color[1];&lt;br /&gt;
                data[p + 2] = color[2];&lt;br /&gt;
                data[p + 3] = color[3];&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preparePixelCanvas(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelCanvasCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {};&lt;br /&gt;
        cached.promise = fetchPixelAsset(ref).then(function (asset) {&lt;br /&gt;
            var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            drawPixelAssetToCanvas(canvas, asset);&lt;br /&gt;
            canvas.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;data-decoration-asset-size&#039;, asset.width + &#039;x&#039; + asset.height);&lt;br /&gt;
            cached.canvas = canvas;&lt;br /&gt;
            cached.width = asset.width;&lt;br /&gt;
            cached.height = asset.height;&lt;br /&gt;
            return canvas;&lt;br /&gt;
        });&lt;br /&gt;
        pixelCanvasCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedPixelCanvasSync(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached = url ? pixelCanvasCache[url] : null;&lt;br /&gt;
        return cached &amp;amp;&amp;amp; cached.canvas ? cached.canvas : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queryDecorationTarget(selector) {&lt;br /&gt;
        selector = String(selector || &#039;&#039;).trim();&lt;br /&gt;
        if (!selector) return null;&lt;br /&gt;
        try {&lt;br /&gt;
            return document.querySelector(selector);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function usesCanonicalMainPageCoordinates(entry, actualTarget) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var declaredTarget;&lt;br /&gt;
&lt;br /&gt;
        if (placement === MAIN_PAGE_PLACEMENT) return true;&lt;br /&gt;
        if (!actualTarget || !selector) return false;&lt;br /&gt;
&lt;br /&gt;
        declaredTarget = queryDecorationTarget(selector);&lt;br /&gt;
        return declaredTarget === actualTarget;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationLocalPosition(entry, actualTarget) {&lt;br /&gt;
        var x = normalizeNumber(entry &amp;amp;&amp;amp; entry.x, 0);&lt;br /&gt;
        var y = normalizeNumber(entry &amp;amp;&amp;amp; entry.y, 0);&lt;br /&gt;
        var sourceX = x;&lt;br /&gt;
        var sourceY = y;&lt;br /&gt;
        var sourceSelector;&lt;br /&gt;
        var sourceTarget;&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var actualRect;&lt;br /&gt;
        var sourceOriginX;&lt;br /&gt;
        var sourceOriginY;&lt;br /&gt;
        var actualOriginX;&lt;br /&gt;
        var actualOriginY;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문 데코 좌표 보존&lt;br /&gt;
        -----------------------------------------&lt;br /&gt;
        저장된 x/y는 기존 entry.target 좌표계를 기준으로 만들어졌다.&lt;br /&gt;
        데코를 main-body-well 안으로 옮길 때 x/y를 그대로 적용하면&lt;br /&gt;
        더 아래에서 시작하는 새 좌표계 때문에 장식이 바닥으로 밀린다.&lt;br /&gt;
&lt;br /&gt;
        실제 화면상의 위치는 유지하고 부모만 우물로 바꾼다.&lt;br /&gt;
        기존 기준면의 padding-box 원점과 새 우물의 padding-box 원점 차이를&lt;br /&gt;
        정수 px로 더해 새 로컬 좌표를 만든다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            currentPageKey() === &#039;대문&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList.contains(&#039;main-body-well&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
            !usesCanonicalMainPageCoordinates(entry, actualTarget)&lt;br /&gt;
        ) {&lt;br /&gt;
            sourceSelector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() ||&lt;br /&gt;
                &#039;.liberty-content-main&#039;;&lt;br /&gt;
            sourceTarget = queryDecorationTarget(sourceSelector) ||&lt;br /&gt;
                document.querySelector(&#039;.liberty-content-main&#039;) ||&lt;br /&gt;
                document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (sourceTarget &amp;amp;&amp;amp; sourceTarget !== actualTarget) {&lt;br /&gt;
                sourceRect = sourceTarget.getBoundingClientRect();&lt;br /&gt;
                actualRect = actualTarget.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
                sourceOriginX = sourceRect.left + normalizeNumber(sourceTarget.clientLeft, 0);&lt;br /&gt;
                sourceOriginY = sourceRect.top + normalizeNumber(sourceTarget.clientTop, 0);&lt;br /&gt;
                actualOriginX = actualRect.left + normalizeNumber(actualTarget.clientLeft, 0);&lt;br /&gt;
                actualOriginY = actualRect.top + normalizeNumber(actualTarget.clientTop, 0);&lt;br /&gt;
&lt;br /&gt;
                x += Math.round(sourceOriginX - actualOriginX);&lt;br /&gt;
                y += Math.round(sourceOriginY - actualOriginY);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            x: Math.round(x),&lt;br /&gt;
            y: Math.round(y),&lt;br /&gt;
            sourceX: Math.round(sourceX),&lt;br /&gt;
            sourceY: Math.round(sourceY),&lt;br /&gt;
            sourceSelector: sourceSelector || &#039;&#039;&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecorationBaseStyle(node, entry, actualTarget) {&lt;br /&gt;
        var position = getDecorationLocalPosition(entry, actualTarget);&lt;br /&gt;
&lt;br /&gt;
        node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
        node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
        node.style.opacity = String(normalizeNumber(entry.opacity, 1));&lt;br /&gt;
        node.style.zIndex = String(Math.round(normalizeNumber(entry.zIndex, 0)));&lt;br /&gt;
        node.style.pointerEvents = String(entry.pointerEvents || &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (position.sourceSelector) {&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-coordinate-source&#039;, position.sourceSelector);&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-x&#039;, String(position.sourceX));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-y&#039;, String(position.sourceY));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (entry.blendMode) node.style.mixBlendMode = String(entry.blendMode);&lt;br /&gt;
        if (entry.filter) node.style.filter = String(entry.filter);&lt;br /&gt;
        if (entry.transform) node.style.transform = String(entry.transform);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDecorationNodeActiveForNationsState(node) {&lt;br /&gt;
        var era = node ? String(node.getAttribute(&#039;data-decoration-era&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        var continent = node ? String(node.getAttribute(&#039;data-decoration-continent&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; era !== getActiveNationsEra()) return false;&lt;br /&gt;
        if (continent &amp;amp;&amp;amp; continent !== getActiveNationsContinent()) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDecorationNodeVisibility(node, visible) {&lt;br /&gt;
        if (!node) return;&lt;br /&gt;
        visible = visible !== false;&lt;br /&gt;
        /* aria-hidden stays true because wiki decorations are purely visual. */&lt;br /&gt;
        if (node.hidden !== !visible) node.hidden = !visible;&lt;br /&gt;
        if (node.style.display !== (visible ? &#039;&#039; : &#039;none&#039;)) node.style.display = visible ? &#039;&#039; : &#039;none&#039;;&lt;br /&gt;
        node.setAttribute(&#039;data-decoration-visible&#039;, visible ? &#039;1&#039; : &#039;0&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateDecorationVisibility(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var count = 0;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            setDecorationNodeVisibility(node, isDecorationNodeActiveForNationsState(node));&lt;br /&gt;
            count += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return count;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasRenderedDecorations(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return !!(scope &amp;amp;&amp;amp; scope.querySelector &amp;amp;&amp;amp; scope.querySelector(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPixelJsonDecoration(entry, target, visible) {&lt;br /&gt;
        var ref = getPixelJsonRef(entry);&lt;br /&gt;
        var template;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var ctx;&lt;br /&gt;
        if (!ref) return false;&lt;br /&gt;
&lt;br /&gt;
        function makeCanvasFromTemplate(source) {&lt;br /&gt;
            var out = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            var outCtx;&lt;br /&gt;
            out.className = &#039;wiki-decoration wiki-decoration-pixel-json&#039;;&lt;br /&gt;
            out.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
            out.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-type&#039;, &#039;pixel-json&#039;);&lt;br /&gt;
            if (entry.placement) out.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
            if (entry.era) out.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
            if (entry.continent) out.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
            out.width = source.width;&lt;br /&gt;
            out.height = source.height;&lt;br /&gt;
            out.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            out.style.width = source.width + &#039;px&#039;;&lt;br /&gt;
            out.style.height = source.height + &#039;px&#039;;&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-size&#039;, source.width + &#039;x&#039; + source.height);&lt;br /&gt;
            outCtx = out.getContext(&#039;2d&#039;);&lt;br /&gt;
            outCtx.imageSmoothingEnabled = false;&lt;br /&gt;
            outCtx.drawImage(source, 0, 0);&lt;br /&gt;
            applyDecorationBaseStyle(out, entry, target);&lt;br /&gt;
            setDecorationNodeVisibility(out, visible);&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template = getPreparedPixelCanvasSync(ref);&lt;br /&gt;
        if (template) {&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(template));&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Fallback path only.  A full entry pack should prepare the template before the&lt;br /&gt;
           normal UI is released, so users should not see a blank decoration canvas. */&lt;br /&gt;
        preparePixelCanvas(ref).then(function (source) {&lt;br /&gt;
            if (!target || !target.parentNode || !source) return;&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(source));&lt;br /&gt;
        }).catch(function () {});&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preloadPixelAssetsForRegistry(registry) {&lt;br /&gt;
        var promises = [];&lt;br /&gt;
        decorationList(registry).forEach(function (entry) {&lt;br /&gt;
            var ref;&lt;br /&gt;
            if (!entry || !matchesPage(entry) || !isPixelJsonDecoration(entry)) return;&lt;br /&gt;
            ref = getPixelJsonRef(entry);&lt;br /&gt;
            if (!ref) return;&lt;br /&gt;
            promises.push(preparePixelCanvas(ref).catch(function () { return null; }));&lt;br /&gt;
        });&lt;br /&gt;
        return Promise.all(promises);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decorationList(registry) {&lt;br /&gt;
        if (!registry || typeof registry !== &#039;object&#039;) return [];&lt;br /&gt;
        if (Array.isArray(registry)) return registry;&lt;br /&gt;
        if (Array.isArray(registry.decorations)) return registry.decorations;&lt;br /&gt;
        return [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesPage(entry) {&lt;br /&gt;
        var page = currentPageKey();&lt;br /&gt;
        var underscored = page.replace(/ /g, &#039;_&#039;);&lt;br /&gt;
        var pages = entry &amp;amp;&amp;amp; entry.pages;&lt;br /&gt;
        var target = entry &amp;amp;&amp;amp; entry.page;&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        if (normalizeBool(entry &amp;amp;&amp;amp; entry.global, false)) return true;&lt;br /&gt;
        if (String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;boot-gate&#039; || String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;loading-screen&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (normalizePageName(target).toLowerCase() === &#039;__boot__&#039; || normalizePageName(target).toLowerCase() === &#039;loading-screen&#039;) return true;&lt;br /&gt;
        if (!target &amp;amp;&amp;amp; !pages) return true;&lt;br /&gt;
&lt;br /&gt;
        if (Array.isArray(pages)) {&lt;br /&gt;
            for (i = 0; i &amp;lt; pages.length; i += 1) {&lt;br /&gt;
                if (normalizePageName(pages[i]) === page || String(pages[i] || &#039;&#039;) === underscored) return true;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        target = normalizePageName(target);&lt;br /&gt;
        return target === page || target === underscored;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearRendered(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureHost(target) {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (!target) return;&lt;br /&gt;
        target.setAttribute(HOST_ATTR, &#039;1&#039;);&lt;br /&gt;
        style = window.getComputedStyle ? window.getComputedStyle(target) : null;&lt;br /&gt;
        if (style &amp;amp;&amp;amp; style.position === &#039;static&#039;) target.style.position = &#039;relative&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecoration(entry) {&lt;br /&gt;
        var resolved;&lt;br /&gt;
        var target;&lt;br /&gt;
        var src;&lt;br /&gt;
        var img;&lt;br /&gt;
        var width;&lt;br /&gt;
        var height;&lt;br /&gt;
&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039; || normalizeBool(entry.enabled, true) === false) return false;&lt;br /&gt;
        resolved = resolveDecorationPlacement(entry);&lt;br /&gt;
        if (!resolved) return false;&lt;br /&gt;
        target = resolved.target;&lt;br /&gt;
        if (!target) return false;&lt;br /&gt;
&lt;br /&gt;
        ensureHost(target);&lt;br /&gt;
&lt;br /&gt;
        if (isPixelJsonDecoration(entry)) {&lt;br /&gt;
            return applyPixelJsonDecoration(entry, target, resolved.visible !== false);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        src = normalizeSrc(entry.src);&lt;br /&gt;
        if (!src) return false;&lt;br /&gt;
&lt;br /&gt;
        img = document.createElement(&#039;img&#039;);&lt;br /&gt;
        img.className = &#039;wiki-decoration&#039;;&lt;br /&gt;
        img.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
        img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, &#039;&#039;);&lt;br /&gt;
        img.setAttribute(&#039;decoding&#039;, &#039;async&#039;);&lt;br /&gt;
        img.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        img.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
        if (entry.placement) img.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
        if (entry.era) img.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
        if (entry.continent) img.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
        img.src = src;&lt;br /&gt;
&lt;br /&gt;
        applyDecorationBaseStyle(img, entry, target);&lt;br /&gt;
        width = normalizeNumber(entry.width, NaN);&lt;br /&gt;
        height = normalizeNumber(entry.height, NaN);&lt;br /&gt;
        if (Number.isFinite(width) &amp;amp;&amp;amp; width &amp;gt; 0) img.style.width = width + &#039;px&#039;;&lt;br /&gt;
        if (Number.isFinite(height) &amp;amp;&amp;amp; height &amp;gt; 0) img.style.height = height + &#039;px&#039;;&lt;br /&gt;
        if (entry.objectFit) img.style.objectFit = String(entry.objectFit);&lt;br /&gt;
        setDecorationNodeVisibility(img, resolved.visible !== false);&lt;br /&gt;
&lt;br /&gt;
        target.appendChild(img);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mainPageLayoutSignature() {&lt;br /&gt;
        var source = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
        var well = ensureMainPageBodyWell();&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var wellRect;&lt;br /&gt;
&lt;br /&gt;
        if (!source || !well) return &#039;&#039;;&lt;br /&gt;
        sourceRect = source.getBoundingClientRect();&lt;br /&gt;
        wellRect = well.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            Math.round((wellRect.left + normalizeNumber(well.clientLeft, 0)) - (sourceRect.left + normalizeNumber(source.clientLeft, 0))),&lt;br /&gt;
            Math.round((wellRect.top + normalizeNumber(well.clientTop, 0)) - (sourceRect.top + normalizeNumber(source.clientTop, 0))),&lt;br /&gt;
            Math.round(wellRect.width),&lt;br /&gt;
            Math.round(wellRect.height)&lt;br /&gt;
        ].join(&#039;|&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMainPageDecorationLayoutReady() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var panel = portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var topMount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!portal || !panel || !ensureMainPageBodyWell()) return false;&lt;br /&gt;
        if (!document.body || !document.body.classList.contains(&#039;clbi-main-page&#039;)) return false;&lt;br /&gt;
        if (topMount &amp;amp;&amp;amp; !topMount.querySelector(&#039;.portal-category-nav&#039;)) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForMainPageDecorationLayout() {&lt;br /&gt;
        var started;&lt;br /&gt;
        var previous = &#039;&#039;;&lt;br /&gt;
        var stableFrames = 0;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return Promise.resolve(true);&lt;br /&gt;
        started = Date.now();&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function check() {&lt;br /&gt;
                var signature = mainPageLayoutSignature();&lt;br /&gt;
&lt;br /&gt;
                if (signature &amp;amp;&amp;amp; signature === previous) stableFrames += 1;&lt;br /&gt;
                else stableFrames = 0;&lt;br /&gt;
                previous = signature;&lt;br /&gt;
&lt;br /&gt;
                if ((isMainPageDecorationLayoutReady() &amp;amp;&amp;amp; stableFrames &amp;gt;= 2) || Date.now() - started &amp;gt;= 1200) {&lt;br /&gt;
                    resolve(true);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (window.requestAnimationFrame) window.requestAnimationFrame(check);&lt;br /&gt;
                else window.setTimeout(check, 16);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebaseLegacyMainPageDecorations() {&lt;br /&gt;
        var well;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return;&lt;br /&gt;
        well = ensureMainPageBodyWell();&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-coordinate-source]&#039;).forEach(function (node) {&lt;br /&gt;
            var position = getDecorationLocalPosition({&lt;br /&gt;
                target: node.getAttribute(&#039;data-decoration-coordinate-source&#039;) || &#039;.liberty-content-main&#039;,&lt;br /&gt;
                x: normalizeNumber(node.getAttribute(&#039;data-decoration-source-x&#039;), 0),&lt;br /&gt;
                y: normalizeNumber(node.getAttribute(&#039;data-decoration-source-y&#039;), 0)&lt;br /&gt;
            }, well);&lt;br /&gt;
&lt;br /&gt;
            node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
            node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleMainPageDecorationRebase() {&lt;br /&gt;
        if (scheduledMainPageRebase) return;&lt;br /&gt;
        scheduledMainPageRebase = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindMainPagePlacementRefresh() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var nodes;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039; || !portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = null;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (observedMainPagePortal !== portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = portal;&lt;br /&gt;
&lt;br /&gt;
            if (typeof ResizeObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementObserver = new ResizeObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                nodes = [&lt;br /&gt;
                    document.querySelector(&#039;.liberty-content-main&#039;),&lt;br /&gt;
                    portal,&lt;br /&gt;
                    portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-panel&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-well&#039;)&lt;br /&gt;
                ];&lt;br /&gt;
                nodes.forEach(function (node) {&lt;br /&gt;
                    if (node) mainPagePlacementObserver.observe(node);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementMutationObserver = new MutationObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                mainPagePlacementMutationObserver.observe(portal, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        scheduleMainPageDecorationRebase();&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 80);&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderForCurrentLayout(registry, token) {&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return Promise.resolve(registry);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return waitForMainPageDecorationLayout().then(function () {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return registry;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(registry) {&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        lastRegistry = registry || { decorations: [] };&lt;br /&gt;
        lastRenderedPageKey = currentPageKey();&lt;br /&gt;
        bindNationsPlacementRefresh();&lt;br /&gt;
        clearRendered(document);&lt;br /&gt;
        decorationList(lastRegistry).forEach(function (entry) {&lt;br /&gt;
            if (token === runtimeToken &amp;amp;&amp;amp; matchesPage(entry)) applyDecoration(entry);&lt;br /&gt;
        });&lt;br /&gt;
        bindMainPagePlacementRefresh();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPrepared() {&lt;br /&gt;
        var registry = getPreparedRegistrySync() || lastRegistry;&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        if (!registry) return false;&lt;br /&gt;
        renderForCurrentLayout(registry, token);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function syncDecorationState() {&lt;br /&gt;
        if (lastRenderedPageKey === currentPageKey() &amp;amp;&amp;amp; hasRenderedDecorations(document)) {&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (lastRegistry) {&lt;br /&gt;
            renderForCurrentLayout(lastRegistry, runtimeToken);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        return renderPrepared();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleRenderFromCache() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleDecorationVisibilityUpdate() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindNationsPlacementRefresh() {&lt;br /&gt;
        var stack = document.querySelector(&#039;.clbi-nations-panel-stack&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!stack || observedNationsStack === stack) return;&lt;br /&gt;
        observedNationsStack = stack;&lt;br /&gt;
&lt;br /&gt;
        if (nationsPlacementObserver) {&lt;br /&gt;
            nationsPlacementObserver.disconnect();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
            nationsPlacementObserver = new MutationObserver(scheduleDecorationVisibilityUpdate);&lt;br /&gt;
            nationsPlacementObserver.observe(stack, {&lt;br /&gt;
                subtree: true,&lt;br /&gt;
                attributes: true,&lt;br /&gt;
                attributeFilter: [&#039;class&#039;, &#039;hidden&#039;, &#039;aria-selected&#039;, &#039;data-current-era&#039;, &#039;data-nations-current-era&#039;, &#039;data-era-year&#039;]&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedRegistrySync() {&lt;br /&gt;
        var registry = null;&lt;br /&gt;
        var url;&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.getJsonSync === &#039;function&#039;) {&lt;br /&gt;
                registry = window.EntryStore.getJsonSync(REGISTRY_TITLE);&lt;br /&gt;
                if (!registry &amp;amp;&amp;amp; mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                    url = mw.util.getUrl(REGISTRY_TITLE, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
                    registry = window.EntryStore.getJsonSync(url);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return registry &amp;amp;&amp;amp; typeof registry === &#039;object&#039; ? registry : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchRegistry() {&lt;br /&gt;
        var url;&lt;br /&gt;
        var prepared = getPreparedRegistrySync();&lt;br /&gt;
        if (prepared) return Promise.resolve(prepared);&lt;br /&gt;
        if (!mw || !mw.util || typeof fetch !== &#039;function&#039;) return Promise.resolve({ decorations: [] });&lt;br /&gt;
        url = mw.util.getUrl(REGISTRY_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
            .then(function (response) {&lt;br /&gt;
                if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                return response.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (!text || !text.trim()) return { decorations: [] };&lt;br /&gt;
                return JSON.parse(text);&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                return { decorations: [] };&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function reload() {&lt;br /&gt;
        var token;&lt;br /&gt;
        runtimeToken += 1;&lt;br /&gt;
        token = runtimeToken;&lt;br /&gt;
        return fetchRegistry().then(function (registry) {&lt;br /&gt;
            return preloadPixelAssetsForRegistry(registry).then(function () {&lt;br /&gt;
                return renderForCurrentLayout(registry, token);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
        var target = event.target &amp;amp;&amp;amp; event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent], [data-nations-era-move]&#039;) : null;&lt;br /&gt;
        if (target) window.setTimeout(scheduleDecorationVisibilityUpdate, 0);&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    function decorationDiagnostics() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: &#039;20260713-main-body-well-coordinate-003&#039;,&lt;br /&gt;
            hasRegistry: !!lastRegistry,&lt;br /&gt;
            entries: decorationList(lastRegistry).length,&lt;br /&gt;
            rendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            preparedRegistry: !!getPreparedRegistrySync(),&lt;br /&gt;
            pixelAssets: Object.keys(pixelAssetCache || {}).length,&lt;br /&gt;
            pixelCanvases: Object.keys(pixelCanvasCache || {}).length,&lt;br /&gt;
            visibilityOnlySync: true,&lt;br /&gt;
            mainPageCoordinateSurface: MAIN_PAGE_TARGET,&lt;br /&gt;
            mainPagePlacementObserved: !!observedMainPagePortal,&lt;br /&gt;
            lastRenderedPage: lastRenderedPageKey,&lt;br /&gt;
            visibleRendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-visible=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            scheduled: !!scheduledRender&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.Decorations = window.Decorations || {};&lt;br /&gt;
    window.Decorations.reload = reload;&lt;br /&gt;
    window.Decorations.render = render;&lt;br /&gt;
    window.Decorations.renderPrepared = renderPrepared;&lt;br /&gt;
    window.Decorations.sync = syncDecorationState;&lt;br /&gt;
    window.Decorations.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.Decorations.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.Decorations.clear = clearRendered;&lt;br /&gt;
    window.Decorations.apply = applyDecoration;&lt;br /&gt;
    window.Decorations.pageKey = currentPageKey;&lt;br /&gt;
    window.Decorations.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.Decorations.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.Decorations.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.Decorations.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_DECORATIONS = window.CLBI_DECORATIONS || {};&lt;br /&gt;
    window.CLBI_DECORATIONS.reload = reload;&lt;br /&gt;
    window.CLBI_DECORATIONS.render = render;&lt;br /&gt;
    window.CLBI_DECORATIONS.renderPrepared = renderPrepared;&lt;br /&gt;
    window.CLBI_DECORATIONS.sync = syncDecorationState;&lt;br /&gt;
    window.CLBI_DECORATIONS.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.CLBI_DECORATIONS.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.CLBI_DECORATIONS.clear = clearRendered;&lt;br /&gt;
    window.CLBI_DECORATIONS.apply = applyDecoration;&lt;br /&gt;
    window.CLBI_DECORATIONS.pageKey = currentPageKey;&lt;br /&gt;
    window.CLBI_DECORATIONS.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.CLBI_DECORATIONS.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.CLBI_DECORATIONS.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.CLBI_DECORATIONS.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, reload);&lt;br /&gt;
    } else {&lt;br /&gt;
        reload();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            reload();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Unified Shortcuts loader&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!window.mw || !mw.loader) return;&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Nation_List_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:History_Event_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Shortcuts.js&#039;);&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Main page manifesto bitmap renderer&lt;br /&gt;
   Integrated build: no extra raw-script page, no image/data-URL decoding.&lt;br /&gt;
   New shared systems do not use the legacy CLBI prefix.&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Retired compatibility block&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    선언문은 실제 HTML과 ManifestoIntro의 실선 요소만 사용한다.&lt;br /&gt;
    이 구식 렌더러는 제목·본문과 완성형 구분선을 canvas에 한 번에 그려&lt;br /&gt;
    새 애니메이션의 선 뒤에 트랙이 나타날 수 있었다.&lt;br /&gt;
&lt;br /&gt;
    기존 Common.js를 장기 캐시한 뒤 새 파일로 넘어오는 경우를 위해&lt;br /&gt;
    남아 있는 canvas와 상태만 제거하고 즉시 종료한다. 새 공개 이름에는&lt;br /&gt;
    프로젝트 접두사를 사용하지 않는 규칙을 유지한다.&lt;br /&gt;
    */&lt;br /&gt;
    function removeRetiredBitmap() {&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-bitmap&#039;),&lt;br /&gt;
            function (canvas) {&lt;br /&gt;
                if (canvas.parentNode) canvas.parentNode.removeChild(canvas);&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-inner&#039;),&lt;br /&gt;
            function (inner) {&lt;br /&gt;
                inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;data-bitmap-width&#039;,&lt;br /&gt;
                    &#039;data-bitmap-optical-shift-x&#039;,&lt;br /&gt;
                    &#039;data-bitmap-state&#039;,&lt;br /&gt;
                    &#039;data-bitmap-error&#039;&lt;br /&gt;
                ].forEach(function (name) {&lt;br /&gt;
                    inner.removeAttribute(name);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    removeRetiredBitmap();&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(removeRetiredBitmap);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreRetiredBitmapHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:&#039;retired-20260713-divider-no-track-001&#039;,&lt;br /&gt;
        recalculate:removeRetiredBitmap,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            return {&lt;br /&gt;
                integrated:false,&lt;br /&gt;
                state:&#039;retired&#039;,&lt;br /&gt;
                canvas:!!document.querySelector(&#039;.main-portal .main-manifesto-bitmap&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    var VERSION = &#039;20260712-bitmap18-inline-002&#039;;&lt;br /&gt;
    var SELECTOR = &#039;.main-portal .main-manifesto-inner&#039;;&lt;br /&gt;
    var CELL_W = 24;&lt;br /&gt;
    var CELL_H = 24;&lt;br /&gt;
    var GLYPHS = {&amp;quot;진&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,25056,25072,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;창&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,13280,78328,258272,12768,14128,13848,4360,3840,7040,12672,4480,8064,3840,0,0],&amp;quot;바&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12672,12672,14080,13056,13064,258456,127472,12784,12504,12408,12312,12288,12288,4096,0,0],&amp;quot;다&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,12792,12400,12320,258096,128528,14328,13048,12304,12288,12288,12288,4096,0,0],&amp;quot;는&amp;quot;:[19,0,0,0,0,0,0,96,192,192,14528,16064,960,0,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;왕&amp;quot;:[19,0,0,0,0,0,12288,28768,25568,25456,25392,25392,254944,24960,16064,10236,12344,7680,14080,12672,12672,16128,7936,0,0],&amp;quot;과&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,26560,26352,26112,26112,26208,255680,254912,29248,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;노&amp;quot;:[19,0,0,0,0,0,0,0,0,96,192,192,4288,15552,16320,7040,7168,3072,50688,131064,124,0,0,0,0],&amp;quot;예&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,24048,24504,23320,23320,24504,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;를&amp;quot;:[19,0,0,0,0,0,7168,16320,6144,8128,448,15552,1984,122880,262140,16636,8128,8128,3840,8064,1792,960,65408,4096,0],&amp;quot;같&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,110976,127168,12384,12344,4104,3584,0,8064,1920,256,14720,8064,0,0],&amp;quot;은&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,130944,115708,16,448,448,128,128,16256,7936,0,0],&amp;quot;깊&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,25024,24800,24624,8216,12288,16256,4864,15872,7680,31232,32640,256,0],&amp;quot;이&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,25072,25584,25368,25368,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;로&amp;quot;:[19,0,0,0,0,0,0,0,15360,16320,6272,7168,8128,384,14528,16320,7040,3072,50688,131064,124,0,0,0,0],&amp;quot;삼&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,78064,258272,13296,14136,13324,4096,16256,16256,4224,4480,8064,8064,0,0],&amp;quot;켰&amp;quot;:[19,0,0,0,0,0,12288,24576,26112,26600,29560,32656,25072,31992,26720,8224,8216,13056,15104,5056,15232,28544,58992,0,0],&amp;quot;고&amp;quot;:[19,0,0,0,0,0,0,0,0,7680,16352,12416,12288,12288,12544,13056,6912,4864,49920,131064,124,0,0,0,0],&amp;quot;,&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,8,0,0,0],&amp;quot;겨&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,32560,31104,24768,31840,31792,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;울&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,114688,262140,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;사&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12352,12480,12480,12480,12400,258272,127920,14104,13828,13312,12288,12288,12288,4096,0,0],&amp;quot;람&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13296,13240,78208,258552,12304,14224,13296,4096,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;짐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,26592,26160,9756,0,32512,32640,12672,12672,16128,16128,0,0],&amp;quot;승&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,130816,115708,272,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;의&amp;quot;:[19,0,0,0,0,0,28672,57344,24608,25536,26592,26160,26160,26464,25568,24576,24576,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;살&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,110832,127456,13232,13848,5132,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;을&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,32768,130944,115708,0,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;빛&amp;quot;:[19,0,0,0,0,0,12288,24576,25344,25344,26376,25496,25592,25584,25552,25088,7168,12288,32640,6400,7680,32256,25472,0,0],&amp;quot;으&amp;quot;:[19,0,0,0,0,0,0,0,128,3968,8064,4544,4288,6336,7616,3968,0,0,57344,131068,56,0,0,0,0],&amp;quot;얼&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25008,32536,31512,25016,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;렸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,32568,27008,31224,31792,26544,9200,8192,13056,15104,4928,15232,28544,58976,0,0],&amp;quot;.&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0],&amp;quot;그&amp;quot;:[19,0,0,0,0,0,0,0,0,7168,16352,12672,12288,12288,12288,12288,6144,4096,57344,131068,56,0,0,0,0],&amp;quot;들&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130048,118780,120,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;돌&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,8128,130048,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;벽&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25344,32520,31512,25560,32632,25584,9104,8192,31744,32640,8192,8192,8192,8192,0,0],&amp;quot;세&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22720,22720,22752,24432,22752,22960,23320,23300,22528,18432,16384,16384,24576,0,0],&amp;quot;우&amp;quot;:[19,0,0,0,0,0,0,384,7936,6528,12480,6336,7552,3968,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;땅&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,28560,9200,74160,254128,30936,16376,12280,12544,7936,13184,12672,12672,8064,3840,0,0],&amp;quot;찢&amp;quot;:[19,0,0,0,0,0,12288,24576,24704,26616,28656,26560,25456,28656,28056,26848,8192,32512,16128,7680,15872,25344,16768,0,0],&amp;quot;어&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,25520,32536,27416,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;길&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,24768,24672,24624,8216,16256,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;냈&amp;quot;:[19,0,0,0,0,0,8192,58368,19456,27672,27704,31792,31792,32560,28656,27120,8704,13824,14080,13248,15232,28544,50272,0,0],&amp;quot;며&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,26600,32536,25368,25400,32560,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;쇠&amp;quot;:[19,0,0,0,0,0,28672,57344,24704,24960,24960,25056,25536,26208,27888,24960,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;에&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,23024,23480,24344,24344,22968,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;제&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23488,23032,22976,24512,24184,23024,23344,23320,23052,18432,16384,16384,24576,0,0],&amp;quot;름&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,384,130560,116732,56,15872,16320,4288,4544,7552,8064,0,0],&amp;quot;새&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,27840,27840,31968,31856,23776,23984,20248,20228,16384,16384,16384,16384,24576,0,0],&amp;quot;겼&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,30712,32640,25024,31968,26672,8216,8200,15104,15104,6976,15232,28608,58992,0,0],&amp;quot;감&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,4120,4096,16256,16320,4224,4480,8064,8064,0,0],&amp;quot;옥&amp;quot;:[19,0,0,0,0,0,1792,7936,6528,6272,6528,8064,3584,1536,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;되&amp;quot;:[19,0,0,0,0,0,28672,57344,25088,26608,25072,24672,25632,26416,26608,25456,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;군&amp;quot;:[19,0,0,0,0,0,0,7680,16352,12288,12288,12288,6144,126976,262140,18172,1536,1728,1472,192,192,16256,8064,0,0],&amp;quot;대&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28032,28144,31856,31776,23600,24344,20440,19960,16400,16384,16384,16384,24576,0,0],&amp;quot;나&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12296,12312,12336,12304,259600,128784,14256,12528,12320,12288,12288,12288,4096,0,0],&amp;quot;르&amp;quot;:[19,0,0,0,0,0,0,0,6144,16320,6336,7168,8128,448,12480,16320,3968,0,57344,131068,56,0,0,0,0],&amp;quot;오&amp;quot;:[19,0,0,0,0,0,0,0,256,7936,15232,12672,12416,12672,8064,7936,3072,1024,49664,131064,124,0,0,0,0],&amp;quot;래&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28144,27064,32128,32248,23664,23568,20240,20464,16880,16384,16384,16384,24576,0,0],&amp;quot;된&amp;quot;:[19,0,0,0,0,0,28672,57344,26368,25584,24800,25696,28656,26608,29440,32736,25080,24832,25344,768,768,65280,15872,0,0],&amp;quot;원&amp;quot;:[19,0,0,0,0,0,28672,57440,25536,26464,26160,26160,29664,32640,26592,26620,25360,25024,25536,768,768,65280,15872,0,0],&amp;quot;한&amp;quot;:[19,0,0,0,0,0,6144,28704,12512,12800,14328,12784,258528,13088,12592,12784,12512,12416,4544,384,128,16256,7936,0,0],&amp;quot;함&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,14328,78032,258528,13104,12720,12784,4192,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;께&amp;quot;:[19,0,0,0,0,0,24576,55296,55296,55296,55488,57336,57280,57184,57184,55600,55704,55500,55392,49152,49152,16384,16384,0,0],&amp;quot;음&amp;quot;:[19,0,0,0,0,0,1920,8064,6528,6336,6336,3968,1792,32768,131040,16892,0,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;손&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,26304,1584,130560,249852,120,448,448,128,128,16256,7936,0,0],&amp;quot;넘&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,31792,31792,24624,28208,26608,8416,0,32512,32640,12672,12672,16256,15616,0,0],&amp;quot;갔&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,12312,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;\n&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],&amp;quot;줄&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,3840,15296,12480,41056,130976,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;처&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,25072,32448,27616,25392,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;부&amp;quot;:[19,0,0,0,0,0,6144,12288,28768,14400,15552,13248,8128,7872,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;터&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25056,24768,25344,25584,31984,31840,25648,28568,26360,24624,24576,24576,8192,8192,0,0],&amp;quot;목&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;둘&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130944,116732,1592,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;레&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23024,22968,22912,24568,22640,22544,23312,23536,23024,18432,16384,16384,24576,0,0],&amp;quot;있&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,25400,25368,25368,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;었&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32568,32536,25496,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;끝&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,3840,3584,8128,1920,384,16320,4032,0,0],&amp;quot;속&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,7936,12672,26336,1568,130560,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;뿌&amp;quot;:[19,0,0,0,0,0,4096,30720,14080,15904,16352,13280,16320,14208,4608,130944,126972,1552,1536,1536,1536,1536,512,0,0],&amp;quot;리&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,24960,25040,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;와&amp;quot;:[19,0,0,0,0,0,12288,28672,24608,25568,25456,26160,26416,25584,254432,254336,28864,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;빨&amp;quot;:[19,0,0,0,0,0,12288,28672,26112,26176,26304,255944,255992,10200,10224,1728,16256,16256,7936,7936,1792,896,65024,0,0],&amp;quot;굶&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,118784,262140,18428,1536,14080,16368,13792,14272,14272,16352,16352,0,0],&amp;quot;주&amp;quot;:[19,0,0,0,0,0,0,14336,16320,3264,1792,7936,12736,24672,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;림&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,25400,25472,25072,24624,26544,9200,8192,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;매&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28544,28644,32156,32024,23832,23992,19952,19504,16384,16384,16384,16384,24576,0,0],&amp;quot;였&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,32752,32568,25368,32664,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;른&amp;quot;:[19,0,0,0,0,0,4096,16320,6336,7744,4032,14528,16320,896,126976,253948,120,64,448,128,128,16256,7936,0,0],&amp;quot;아&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12304,12528,12720,13080,258840,127896,12784,12352,12288,12288,12288,12288,4096,0,0],&amp;quot;직&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,25568,26160,9752,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;태&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27840,28032,32240,31856,23600,24336,20440,20472,16432,16384,16384,16384,24576,0,0],&amp;quot;지&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25584,24992,24768,24816,25568,26160,27672,25612,24576,24576,24576,8192,0,0],&amp;quot;않&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,7168,6240,32352,15456,13408,14176,8160,0,0],&amp;quot;자&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13304,12720,12480,258296,127984,14128,13848,13324,12288,12288,12288,4096,0,0],&amp;quot;졌&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,32752,31136,32240,32736,26160,9756,8704,13056,15104,4928,15232,28544,50784,0,0],&amp;quot;앞&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,6144,8128,2432,7936,2816,15616,16320,128,0],&amp;quot;갈&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,127360,127168,12384,12344,4104,16256,8064,3840,8064,1792,896,65280,0,0],&amp;quot;때&amp;quot;:[19,0,0,0,0,0,24576,52224,55296,55296,57096,56312,63920,63632,63704,56536,57336,57336,49216,49152,49152,16384,16384,0,0],&amp;quot;마&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13284,13084,13080,258840,127792,12784,12464,12288,12288,12288,12288,4096,0,0],&amp;quot;듭&amp;quot;:[19,0,0,0,0,0,6144,16320,1984,384,12480,16064,3008,32768,131008,115708,14336,12352,12480,14720,16256,8064,7808,0,0],&amp;quot;파&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13248,12792,12688,13296,258528,127392,16352,12412,12288,12288,12288,12288,4096,0,0],&amp;quot;각&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,12672,258240,12384,12336,4120,4096,16128,13248,12288,4096,4096,4096,0,0],&amp;quot;쥔&amp;quot;:[19,0,0,0,0,0,28672,57856,26608,25584,25024,26592,28208,27664,28544,28668,25360,24960,25536,768,768,65280,15872,0,0],&amp;quot;당&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,12536,77920,258096,15888,14328,12920,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;기&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,25392,24960,24768,24672,24624,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;면&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26604,32536,25368,32560,31728,25072,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;풀&amp;quot;:[19,0,0,0,0,0,12288,16352,7296,8064,3328,32704,41440,131008,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;려&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,32696,31104,25040,31856,27696,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;날&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12312,12344,110640,128560,14256,13296,4192,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;것&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,24960,32704,26848,24624,24600,11272,3072,1792,3584,15104,29056,24800,0,0],&amp;quot;라&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13280,12728,12672,12792,258168,127504,13840,14320,12784,12288,12288,12288,4096,0,0],&amp;quot;여&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,32688,25368,25368,32664,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;므&amp;quot;:[19,0,0,0,0,0,0,0,4096,31744,32736,6624,3264,1728,16064,7360,0,0,57344,131068,56,0,0,0,0],&amp;quot;번&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25344,26376,26392,32728,25464,25584,25552,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;도&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,4064,960,384,14528,16064,7136,7360,3072,50688,131064,124,0,0,0,0],&amp;quot;느&amp;quot;:[19,0,0,0,0,0,0,0,0,32,192,192,192,14528,16064,7104,0,0,57344,131068,56,0,0,0,0],&amp;quot;슨&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,24768,48,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;해&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27968,28664,32240,32224,23984,23984,19952,19568,16384,16384,16384,16384,24576,0,0],&amp;quot;았&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12528,12784,78616,258840,13208,12784,12512,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;침&amp;quot;:[19,0,0,0,0,0,12288,24768,25024,25088,26592,25584,24800,25568,26416,9232,8,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;내&amp;quot;:[19,0,0,0,0,0,24576,58368,19456,19456,27656,27672,31792,31792,31760,24368,20464,19952,16384,16384,24576,24576,8192,0,0],&amp;quot;멎&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25576,32536,31512,25392,25072,8224,8192,32640,6912,3584,15872,25344,16768,0,0],&amp;quot;서&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,32496,24800,25520,26392,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;긴&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26368,25592,25520,25024,24768,24688,24632,24584,25472,8960,768,768,32512,15872,0,0],&amp;quot;붙&amp;quot;:[19,0,0,0,0,0,6144,12288,14432,16064,13248,7872,38912,130816,116732,1552,3840,3584,8064,1920,384,15808,4032,0,0],&amp;quot;잡&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,111040,258288,13280,13872,5660,4096,12352,12416,14720,16256,8064,7296,0,0],&amp;quot;피&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25592,25392,25568,25440,24992,28640,26744,24576,24576,24576,24576,8192,0,0],&amp;quot;가&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,13304,12728,12736,127168,258144,12336,12312,12300,12288,12288,12288,4096,0,0],&amp;quot;굴&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,126976,262140,18172,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;보&amp;quot;:[19,0,0,0,0,0,0,0,6144,12288,28768,14528,16064,13248,16320,7360,3072,1024,50688,131064,124,0,0,0,0],&amp;quot;하&amp;quot;:[19,0,0,0,0,0,6144,12288,12320,12512,12800,14332,12792,12512,259040,127792,12720,12528,12288,12288,12288,12288,4096,0,0],&amp;quot;입&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25584,25368,25368,25496,25072,8256,4096,28800,24960,14720,16128,16128,15616,0,0],&amp;quot;말&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13308,111384,127768,13240,12784,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;했&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,28664,27888,32224,23984,19888,18672,96,13824,15872,13184,15232,28544,50272,0,0],&amp;quot;조&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,8128,3072,1792,16128,28864,25184,1568,1536,50688,131064,124,0,0,0,0],&amp;quot;올&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6528,8064,3840,1536,128768,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;수&amp;quot;:[19,0,0,0,0,0,0,1536,3072,1792,1792,6912,29056,24800,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;록&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,3968,128512,253948,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;열&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,32688,31512,25368,32696,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;단&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,12792,12400,258096,13872,14232,14328,12336,12672,4480,384,384,16256,7936,0,0],&amp;quot;질&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25584,24992,25072,26464,26160,9240,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;히&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25024,26112,26616,25584,25024,25440,25392,25392,25056,24576,24576,24576,8192,8192,0,0],&amp;quot;운&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;데&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22912,23024,22640,24352,22576,23320,23512,23032,22544,18432,16384,16384,24576,0,0],&amp;quot;끌&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;린&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25568,25464,24960,25592,24624,26416,26608,24672,8320,9088,256,256,32512,15872,0,0],&amp;quot;게&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,22960,24000,24512,22624,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;누&amp;quot;:[19,0,0,0,0,0,0,96,192,192,192,15552,16320,960,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;인&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24624,25072,25400,25368,25368,25072,24800,24576,9088,8960,256,256,32512,15872,0,0],&amp;quot;쳐&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,32752,31936,25568,32560,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;문&amp;quot;:[19,0,0,0,0,0,12288,15872,15328,6336,3264,7872,7360,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;몸&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;식&amp;quot;:[19,0,0,0,0,0,12288,24576,24768,24960,25024,24816,24800,25568,26160,9244,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;억&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32536,32536,25368,25072,8416,0,31744,32640,12288,12288,12288,12288,0,0],&amp;quot;전&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25592,24992,31984,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;품&amp;quot;:[19,0,0,0,0,0,12288,16352,7360,8064,7040,16128,26592,127040,253948,18040,1536,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;발&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,111512,127992,13176,13272,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;머&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,25576,25368,32536,27448,25392,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;두&amp;quot;:[19,0,0,0,0,0,0,14336,8128,960,384,12480,16064,5056,192,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;닐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,24624,24624,27696,28464,26608,8288,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;격&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26616,32640,25024,31968,26672,8216,8200,32256,26496,8192,8192,8192,8192,0,0],&amp;quot;없&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25072,32536,31512,25496,25072,8256,8192,13824,15936,4928,15296,28608,51008,0,0],&amp;quot;니&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24584,24624,24624,24624,25648,28208,26544,25328,24576,24576,24576,8192,8192,0,0],&amp;quot;산&amp;quot;:[19,0,0,0,0,0,6144,28672,12352,12480,12480,12496,258272,13280,14128,13852,12288,12672,4480,384,384,16256,7936,0,0],&amp;quot;복&amp;quot;:[19,0,0,0,0,0,14336,12288,28736,15552,14272,16320,7808,3072,130560,249852,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;종&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,26208,1568,130816,116732,280,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;죽&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,24672,122912,262140,18172,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;망&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,78620,258840,13112,12784,12336,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;베&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22912,22912,24448,24448,24456,24536,23024,23024,22776,22648,18456,16384,16384,24576,0,0],&amp;quot;막&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,13084,258840,13080,12784,12464,4096,15872,16320,12288,4096,4096,4096,0,0],&amp;quot;무&amp;quot;:[19,0,0,0,0,0,0,30720,32608,15296,6336,3520,16320,15488,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;릎&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,8128,0,130560,116732,56,16320,3456,8064,3840,16128,14272,0,0],&amp;quot;꿇&amp;quot;:[19,0,0,0,0,0,0,14720,16368,12640,12672,12672,118912,262140,18428,1536,7936,7152,32736,14784,11712,16352,7648,0,0],&amp;quot;까&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,24576,24768,10232,10176,9824,254752,254768,8600,8388,8288,8192,8192,8192,12288,0,0],&amp;quot;놓&amp;quot;:[19,0,0,0,0,0,0,224,192,12480,15552,8128,7552,130048,253948,16760,1792,15872,16320,3584,6912,3328,1792,0,0],&amp;quot;러&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,29056,32720,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;재&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28608,28152,32192,31936,23672,24048,20272,20248,16908,16384,16384,16384,24576,0,0],&amp;quot;받&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,78744,259064,13176,13304,12672,0,7936,3584,768,24960,32640,1920,0,0],&amp;quot;묵&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,3200,130048,118780,1592,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;스&amp;quot;:[19,0,0,0,0,0,0,0,512,1536,3584,1920,1792,7936,12672,24800,0,0,57344,131068,56,0,0,0,0],&amp;quot;릴&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25592,25360,25536,25072,25136,26608,8432,14464,16256,7936,16128,3840,1792,130560,0,0],&amp;quot;계&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,24496,24256,22720,24416,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;구&amp;quot;:[19,0,0,0,0,0,0,6144,16352,12736,12288,12288,12288,6144,4096,131056,67324,1536,1536,1536,1536,1536,512,0,0],&amp;quot;물&amp;quot;:[19,0,0,0,0,0,14336,32544,15328,3264,7872,16064,32768,131040,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;못&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,7360,3776,16064,7296,3072,130816,116732,1048,3584,1536,1792,7936,12480,8304,0,0],&amp;quot;시&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,24816,24800,25520,26424,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;육&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6336,6528,3968,1536,130048,118780,6584,6528,16256,15296,4096,4096,4096,4096,0,0],&amp;quot;신&amp;quot;:[19,0,0,0,0,0,12288,24576,24640,25024,25024,24768,24800,25568,26416,25628,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;일&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25584,25368,25368,25528,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;모&amp;quot;:[19,0,0,0,0,0,0,0,14336,32256,16352,6368,3264,7872,16064,6144,2048,1024,50688,131064,124,0,0,0,0],&amp;quot;행&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,20472,27888,32224,32176,23984,19696,25184,15872,30208,25344,25344,16128,7680,0,0],&amp;quot;절&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,32160,32752,26464,26160,9752,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;멸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,32760,31512,25400,32688,25072,8224,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;끊&amp;quot;:[19,0,0,0,0,0,0,14592,16368,13280,12672,12672,12672,114688,262140,16636,7168,6240,32352,15456,13408,16224,7648,0,0],&amp;quot;순&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,131008,116732,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;간&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,13304,12688,258240,12384,12336,12312,12296,12672,4480,384,384,16256,7936,0,0]};&lt;br /&gt;
    var BODY_SCALE = 1;&lt;br /&gt;
    var TITLE_SCALE = 2;&lt;br /&gt;
    var BODY_TRACKING = -2;&lt;br /&gt;
    var TITLE_TRACKING = 0;&lt;br /&gt;
    var SPACE_ADVANCE = Math.round(CELL_H * 0.5);&lt;br /&gt;
    var BODY_LINE_HEIGHT = CELL_H + 8;&lt;br /&gt;
    var PARAGRAPH_GAP = 20;&lt;br /&gt;
    var DIVIDER_Y = 52;&lt;br /&gt;
    var BODY_TOP = 68;&lt;br /&gt;
    var BODY_COLOR = &#039;#d8d8d8&#039;;&lt;br /&gt;
    var TITLE_COLOR = &#039;#ffffff&#039;;&lt;br /&gt;
    var DIVIDER_COLOR = &#039;#555555&#039;;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var observed = typeof WeakSet === &#039;function&#039; ? new WeakSet() : null;&lt;br /&gt;
    var resizeObserver = typeof ResizeObserver === &#039;function&#039; ? new ResizeObserver(function (entries) {&lt;br /&gt;
        entries.forEach(function (entry) { schedule(entry.target, false); });&lt;br /&gt;
    }) : null;&lt;br /&gt;
&lt;br /&gt;
    function glyphFor(character) {&lt;br /&gt;
        return Object.prototype.hasOwnProperty.call(GLYPHS, character) ? GLYPHS[character] : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function measureText(text, scale, tracking) {&lt;br /&gt;
        var width = 0;&lt;br /&gt;
        var glyphCount = 0;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                width += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            width += glyph[0] * scale;&lt;br /&gt;
            width += tracking * scale;&lt;br /&gt;
            glyphCount += 1;&lt;br /&gt;
        });&lt;br /&gt;
        if (glyphCount &amp;amp;&amp;amp; tracking) width -= tracking * scale;&lt;br /&gt;
        return Math.max(0, Math.round(width));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function breakLongToken(token, maxWidth) {&lt;br /&gt;
        var pieces = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
        Array.from(token).forEach(function (character) {&lt;br /&gt;
            var candidate = current + character;&lt;br /&gt;
            if (current &amp;amp;&amp;amp; measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;gt; maxWidth) {&lt;br /&gt;
                pieces.push(current);&lt;br /&gt;
                current = character;&lt;br /&gt;
            } else {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        if (current) pieces.push(current);&lt;br /&gt;
        return pieces;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wrapParagraph(text, maxWidth) {&lt;br /&gt;
        var words = String(text || &#039;&#039;).trim().split(/\s+/).filter(Boolean);&lt;br /&gt;
        var lines = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        words.forEach(function (word) {&lt;br /&gt;
            var candidate = current ? current + &#039; &#039; + word : word;&lt;br /&gt;
            var pieces;&lt;br /&gt;
            if (measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (current) {&lt;br /&gt;
                lines.push(current);&lt;br /&gt;
                current = &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (measureText(word, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = word;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            pieces = breakLongToken(word, maxWidth);&lt;br /&gt;
            pieces.forEach(function (piece, index) {&lt;br /&gt;
                if (index === pieces.length - 1) current = piece;&lt;br /&gt;
                else lines.push(piece);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        if (current) lines.push(current);&lt;br /&gt;
        return lines.length ? lines : [&#039;&#039;];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateOpticalShift(lines, width) {&lt;br /&gt;
        var weightedCenter = 0;&lt;br /&gt;
        var totalWeight = 0;&lt;br /&gt;
        var maxLineWidth = 0;&lt;br /&gt;
        var shift;&lt;br /&gt;
        lines.forEach(function (line) {&lt;br /&gt;
            var lineWidth = line.width;&lt;br /&gt;
            var weight = Math.max(1, lineWidth) * BODY_LINE_HEIGHT;&lt;br /&gt;
            weightedCenter += (lineWidth / 2) * weight;&lt;br /&gt;
            totalWeight += weight;&lt;br /&gt;
            maxLineWidth = Math.max(maxLineWidth, lineWidth);&lt;br /&gt;
        });&lt;br /&gt;
        if (!totalWeight) return 0;&lt;br /&gt;
        shift = Math.round((width / 2) - (weightedCenter / totalWeight));&lt;br /&gt;
        return Math.max(0, Math.min(Math.max(0, width - maxLineWidth), shift));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawGlyph(context, glyph, x, y, scale) {&lt;br /&gt;
        var py;&lt;br /&gt;
        var px;&lt;br /&gt;
        var row;&lt;br /&gt;
        for (py = 0; py &amp;lt; CELL_H; py += 1) {&lt;br /&gt;
            row = glyph[py + 1] || 0;&lt;br /&gt;
            if (!row) continue;&lt;br /&gt;
            for (px = 0; px &amp;lt; CELL_W; px += 1) {&lt;br /&gt;
                if (row &amp;amp; (1 &amp;lt;&amp;lt; px)) {&lt;br /&gt;
                    context.fillRect(&lt;br /&gt;
                        Math.round(x + px * scale),&lt;br /&gt;
                        Math.round(y + py * scale),&lt;br /&gt;
                        scale,&lt;br /&gt;
                        scale&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawText(context, text, x, y, scale, tracking, color) {&lt;br /&gt;
        context.fillStyle = color;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                x += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            drawGlyph(context, glyph, x, y, scale);&lt;br /&gt;
            x += (glyph[0] + tracking) * scale;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sourceContent(inner) {&lt;br /&gt;
        var titleNode = inner.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        var paragraphs = [];&lt;br /&gt;
        Array.prototype.forEach.call(inner.children || [], function (child) {&lt;br /&gt;
            if (child &amp;amp;&amp;amp; child.tagName === &#039;P&#039;) {&lt;br /&gt;
                var value = String(child.textContent || &#039;&#039;).trim();&lt;br /&gt;
                if (value) paragraphs.push(value);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        return {&lt;br /&gt;
            title: titleNode ? String(titleNode.textContent || &#039;&#039;).trim() : &#039;&#039;,&lt;br /&gt;
            paragraphs: paragraphs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureCanvas(inner) {&lt;br /&gt;
        var canvas = inner.querySelector(&#039;.main-manifesto-bitmap&#039;);&lt;br /&gt;
        if (!canvas) {&lt;br /&gt;
            canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            canvas.className = &#039;main-manifesto-bitmap&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            canvas.setAttribute(&#039;data-bitmap-version&#039;, VERSION);&lt;br /&gt;
            inner.insertBefore(canvas, inner.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
        return canvas;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(inner, force) {&lt;br /&gt;
        var width;&lt;br /&gt;
        var source;&lt;br /&gt;
        var paragraphs;&lt;br /&gt;
        var flatLines = [];&lt;br /&gt;
        var titleWidth;&lt;br /&gt;
        var height;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var context;&lt;br /&gt;
        var y;&lt;br /&gt;
        var opticalShift;&lt;br /&gt;
&lt;br /&gt;
        if (!inner || !inner.isConnected) return;&lt;br /&gt;
        width = Math.max(1, Math.floor(inner.clientWidth || inner.getBoundingClientRect().width || 0));&lt;br /&gt;
        if (width &amp;lt;= 1) {&lt;br /&gt;
            window.setTimeout(function () { schedule(inner, true); }, 60);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (!force &amp;amp;&amp;amp; Number(inner.getAttribute(&#039;data-bitmap-width&#039;)) === width &amp;amp;&amp;amp; inner.classList.contains(&#039;is-bitmap-ready&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        source = sourceContent(inner);&lt;br /&gt;
        if (!source.title || !source.paragraphs.length) {&lt;br /&gt;
            fail(inner, new Error(&#039;Manifesto source nodes were not found.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        paragraphs = source.paragraphs.map(function (paragraph) {&lt;br /&gt;
            return wrapParagraph(paragraph, width).map(function (line) {&lt;br /&gt;
                var item = { text:line, width:measureText(line, BODY_SCALE, BODY_TRACKING) };&lt;br /&gt;
                flatLines.push(item);&lt;br /&gt;
                return item;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        titleWidth = measureText(source.title, TITLE_SCALE, TITLE_TRACKING);&lt;br /&gt;
        height = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, index) {&lt;br /&gt;
            height += lines.length * BODY_LINE_HEIGHT;&lt;br /&gt;
            if (index &amp;lt; paragraphs.length - 1) height += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
        height = Math.max(height, DIVIDER_Y + 2);&lt;br /&gt;
&lt;br /&gt;
        canvas = ensureCanvas(inner);&lt;br /&gt;
        canvas.width = width;&lt;br /&gt;
        canvas.height = Math.ceil(height);&lt;br /&gt;
        canvas.style.width = width + &#039;px&#039;;&lt;br /&gt;
        canvas.style.height = Math.ceil(height) + &#039;px&#039;;&lt;br /&gt;
        context = canvas.getContext(&#039;2d&#039;, { alpha:true });&lt;br /&gt;
        if (!context) {&lt;br /&gt;
            fail(inner, new Error(&#039;Canvas 2D context is unavailable.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        context.imageSmoothingEnabled = false;&lt;br /&gt;
        context.clearRect(0, 0, canvas.width, canvas.height);&lt;br /&gt;
&lt;br /&gt;
        drawText(&lt;br /&gt;
            context,&lt;br /&gt;
            source.title,&lt;br /&gt;
            Math.round((width - titleWidth) / 2),&lt;br /&gt;
            0,&lt;br /&gt;
            TITLE_SCALE,&lt;br /&gt;
            TITLE_TRACKING,&lt;br /&gt;
            TITLE_COLOR&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /* 완성형 구분선은 폐기했다. 실선 애니메이션은 ManifestoIntro만 담당한다. */&lt;br /&gt;
&lt;br /&gt;
        opticalShift = calculateOpticalShift(flatLines, width);&lt;br /&gt;
        y = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, paragraphIndex) {&lt;br /&gt;
            lines.forEach(function (line) {&lt;br /&gt;
                drawText(context, line.text, opticalShift, y, BODY_SCALE, BODY_TRACKING, BODY_COLOR);&lt;br /&gt;
                y += BODY_LINE_HEIGHT;&lt;br /&gt;
            });&lt;br /&gt;
            if (paragraphIndex &amp;lt; paragraphs.length - 1) y += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-width&#039;, String(width));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-optical-shift-x&#039;, String(opticalShift));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        inner.classList.add(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fail(inner, error) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;failed&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-error&#039;, String(error &amp;amp;&amp;amp; error.message ? error.message : error));&lt;br /&gt;
        try { console.error(&#039;[MainPageBitmap]&#039;, error); } catch (ignore) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(inner, force) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            try { render(inner, force); }&lt;br /&gt;
            catch (error) { fail(inner, error); }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mount(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var nodes = [];&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(SELECTOR)) nodes.push(scope);&lt;br /&gt;
        nodes = nodes.concat(Array.prototype.slice.call(scope.querySelectorAll(SELECTOR)));&lt;br /&gt;
        nodes.forEach(function (inner) {&lt;br /&gt;
            if (resizeObserver &amp;amp;&amp;amp; (!observed || !observed.has(inner))) {&lt;br /&gt;
                resizeObserver.observe(inner);&lt;br /&gt;
                if (observed) observed.add(inner);&lt;br /&gt;
            }&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function recalculate() {&lt;br /&gt;
        Array.prototype.forEach.call(document.querySelectorAll(SELECTOR), function (inner) {&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot() {&lt;br /&gt;
        mount(document);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 120);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) document.addEventListener(&#039;DOMContentLoaded&#039;, boot, { once:true });&lt;br /&gt;
    else boot();&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        window.clearTimeout(resizeTimer);&lt;br /&gt;
        resizeTimer = window.setTimeout(recalculate, 100);&lt;br /&gt;
    }, { passive:true });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                mount(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        recalculate:recalculate,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            var inner = document.querySelector(SELECTOR);&lt;br /&gt;
            return inner ? {&lt;br /&gt;
                integrated:true,&lt;br /&gt;
                state:inner.getAttribute(&#039;data-bitmap-state&#039;) || &#039;pending&#039;,&lt;br /&gt;
                error:inner.getAttribute(&#039;data-bitmap-error&#039;) || &#039;&#039;,&lt;br /&gt;
                width:inner.getAttribute(&#039;data-bitmap-width&#039;) || &#039;&#039;,&lt;br /&gt;
                opticalShiftX:inner.getAttribute(&#039;data-bitmap-optical-shift-x&#039;) || &#039;&#039;,&lt;br /&gt;
                canvas:!!inner.querySelector(&#039;.main-manifesto-bitmap&#039;)&lt;br /&gt;
            } : { integrated:true, state:&#039;not-mounted&#039; };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    window.MainPageManifesto = window.MainPageManifesto || {};&lt;br /&gt;
    window.MainPageManifesto.recalculate = recalculate;&lt;br /&gt;
})(window, document, window.mw);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryPillar.js&amp;diff=4691</id>
		<title>미디어위키:CategoryPillar.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryPillar.js&amp;diff=4691"/>
		<updated>2026-07-17T09:49:32Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-fixed / js/CategoryPillar.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
CategoryPillar&lt;br /&gt;
canvas CategoryNav 내부 프로젝트 기둥 호환 모듈&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        return scope.querySelectorAll(&lt;br /&gt;
            &#039;.main-portal [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-canvas&#039;&lt;br /&gt;
        ).length;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.CategoryPillar = {&lt;br /&gt;
        version:&#039;20260717-canvas-integrated-001&#039;,&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderAll:renderAll,&lt;br /&gt;
        render:function () { return renderAll(document) &amp;gt; 0; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            renderAll(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4690</id>
		<title>미디어위키:CategoryNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4690"/>
		<updated>2026-07-17T09:49:32Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-fixed / js/CategoryNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - CategoryNav&lt;br /&gt;
대문 카테고리 canvas 픽셀 네비 생성기&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var ITEMS = [&lt;br /&gt;
        { key:&#039;era&#039;, label:&#039;시대&#039;, title:&#039;시대&#039;, slant:&#039;right&#039; },&lt;br /&gt;
        { key:&#039;settings&#039;, label:&#039;설정&#039;, title:&#039;설정&#039;, slant:&#039;right&#039; },&lt;br /&gt;
        { key:&#039;project&#039;, label:&#039;프로젝트&#039;, title:&#039;프로젝트:소개&#039;, slant:&#039;both&#039;, pillar:true },&lt;br /&gt;
        { key:&#039;corporations&#039;, label:&#039;기업 및 공동체&#039;, title:&#039;기업_및_공동체&#039;, slant:&#039;left&#039; },&lt;br /&gt;
        { key:&#039;military&#039;, label:&#039;군, 정치집단&#039;, title:&#039;군_정치집단&#039;, slant:&#039;left&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
&lt;br /&gt;
    function api() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) return window.buildWikiPath(title);&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearNode(node) {&lt;br /&gt;
        while (node &amp;amp;&amp;amp; node.firstChild) node.removeChild(node.firstChild);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateLayout(width) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var f = frame.FRAME;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var contentX = d.WELL_TOP;&lt;br /&gt;
        var contentWidth = Math.max(ITEMS.length * 8, width - contentX * 2);&lt;br /&gt;
        var btnW = Math.max(4, Math.floor((contentWidth - f.GAP * 4) / ITEMS.length));&lt;br /&gt;
        var segments = [];&lt;br /&gt;
        var xoff = contentX;&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; ITEMS.length; i++) {&lt;br /&gt;
            segments.push({&lt;br /&gt;
                index:i,&lt;br /&gt;
                key:ITEMS[i].key,&lt;br /&gt;
                label:ITEMS[i].label,&lt;br /&gt;
                slant:ITEMS[i].slant,&lt;br /&gt;
                x:xoff,&lt;br /&gt;
                y:d.BUTTON_EDGE_TOP,&lt;br /&gt;
                btnW:btnW,&lt;br /&gt;
                pixelWidth:btnW + 2,&lt;br /&gt;
                pixelHeight:d.BUTTON_TOTAL&lt;br /&gt;
            });&lt;br /&gt;
            xoff += btnW + f.GAP;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            width:width,&lt;br /&gt;
            height:d.ROW_HEIGHT,&lt;br /&gt;
            contentX:contentX,&lt;br /&gt;
            contentWidth:contentWidth,&lt;br /&gt;
            buttonWidth:btnW,&lt;br /&gt;
            usedRight:xoff - f.GAP,&lt;br /&gt;
            segments:segments&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function paintProjectPillar(image, width, height, segment) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE);&lt;br /&gt;
        var leftTop = segment.x - topStep;&lt;br /&gt;
        var rightTop = segment.x + segment.btnW + topStep;&lt;br /&gt;
        var y;&lt;br /&gt;
        var x;&lt;br /&gt;
&lt;br /&gt;
        for (y = 0; y &amp;lt; height; y++) {&lt;br /&gt;
            var step = Math.floor(y / frame.FRAME.SLANT_RISE);&lt;br /&gt;
            var left = leftTop + step;&lt;br /&gt;
            var right = rightTop - step;&lt;br /&gt;
            for (x = left; x &amp;lt;= right; x++) frame.setPixel(image, width, height, x, y, &#039;S&#039;);&lt;br /&gt;
            frame.setPixel(image, width, height, left, y, &#039;D&#039;);&lt;br /&gt;
            frame.setPixel(image, width, height, right, y, &#039;H&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            leftTop:leftTop,&lt;br /&gt;
            rightTop:rightTop,&lt;br /&gt;
            leftAtButton:segment.x,&lt;br /&gt;
            rightAtButton:segment.x + segment.btnW&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function labelCenter(segment) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var ys = Math.floor(frame.FRAME.BUTTON_FACE / 2);&lt;br /&gt;
        var mask = frame.buttonMask(segment.btnW, ys, segment.slant, 0);&lt;br /&gt;
        return segment.x + Math.round((mask.left + mask.right) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function draw(canvas, layout, hoverIndex) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        var image = frame.createImage(ctx, layout.width, layout.height, &#039;BG&#039;);&lt;br /&gt;
        var projectGeometry = null;&lt;br /&gt;
&lt;br /&gt;
        frame.paintStandardFrame(image, layout.width, layout.height);&lt;br /&gt;
        layout.segments.forEach(function (segment) {&lt;br /&gt;
            var grid = frame.buildButtonPixels(&lt;br /&gt;
                segment.btnW,&lt;br /&gt;
                frame.FRAME.BUTTON_FACE,&lt;br /&gt;
                segment.slant,&lt;br /&gt;
                0&lt;br /&gt;
            );&lt;br /&gt;
            frame.paintGrid(&lt;br /&gt;
                image,&lt;br /&gt;
                layout.width,&lt;br /&gt;
                layout.height,&lt;br /&gt;
                grid,&lt;br /&gt;
                segment.x,&lt;br /&gt;
                segment.y,&lt;br /&gt;
                true&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        projectGeometry = paintProjectPillar(&lt;br /&gt;
            image,&lt;br /&gt;
            layout.width,&lt;br /&gt;
            layout.height,&lt;br /&gt;
            layout.segments[2]&lt;br /&gt;
        );&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
&lt;br /&gt;
        layout.segments.forEach(function (segment, index) {&lt;br /&gt;
            var center = index === 2&lt;br /&gt;
                ? Math.round((projectGeometry.leftAtButton + projectGeometry.rightAtButton) / 2)&lt;br /&gt;
                : labelCenter(segment);&lt;br /&gt;
            frame.drawLabel(&lt;br /&gt;
                ctx,&lt;br /&gt;
                segment.label,&lt;br /&gt;
                center,&lt;br /&gt;
                Math.round((frame.DIMENSIONS.BUTTON_SURFACE_TOP + frame.DIMENSIONS.BUTTON_SURFACE_BOTTOM) / 2) + 1,&lt;br /&gt;
                hoverIndex === index &amp;amp;&amp;amp; index !== 2,&lt;br /&gt;
                layout.width &amp;lt;= 820 ? 11 : 12&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
        canvas.__projectGeometry = projectGeometry;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function publishSeams(layout) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var settings = layout.segments[1];&lt;br /&gt;
        var project = layout.segments[2];&lt;br /&gt;
        var corporations = layout.segments[3];&lt;br /&gt;
        var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE);&lt;br /&gt;
        var settingsLeftOriginX = settings.x - topStep;&lt;br /&gt;
        var corpRightOriginX = corporations.x + corporations.btnW + topStep;&lt;br /&gt;
        var projectLeftOriginX = project.x - topStep;&lt;br /&gt;
        var projectRightOriginX = project.x + project.btnW + topStep;&lt;br /&gt;
        var seamStep = Math.floor(d.BOTTOM_START_ABS_Y / frame.FRAME.SLANT_RISE);&lt;br /&gt;
&lt;br /&gt;
        frame.setSeams({&lt;br /&gt;
            width:layout.width,&lt;br /&gt;
            rowHeight:d.ROW_HEIGHT,&lt;br /&gt;
            buttonTop:d.BUTTON_EDGE_TOP,&lt;br /&gt;
            buttonBottom:d.BUTTON_EDGE_BOTTOM,&lt;br /&gt;
            overlap:d.OVERLAP,&lt;br /&gt;
            bottomStartAbsY:d.BOTTOM_START_ABS_Y,&lt;br /&gt;
            phase:d.BOTTOM_START_ABS_Y % frame.FRAME.SLANT_RISE,&lt;br /&gt;
            settingsLeftOriginX:settingsLeftOriginX,&lt;br /&gt;
            settingsLeftTopX:settings.x,&lt;br /&gt;
            settingsLeftSeamX:settingsLeftOriginX + seamStep,&lt;br /&gt;
            settingsLeftDir:1,&lt;br /&gt;
            corpRightOriginX:corpRightOriginX,&lt;br /&gt;
            corpRightTopX:corporations.x + corporations.btnW,&lt;br /&gt;
            corpRightSeamX:corpRightOriginX - seamStep,&lt;br /&gt;
            corpRightDir:-1,&lt;br /&gt;
            projectLeftOriginX:projectLeftOriginX,&lt;br /&gt;
            projectRightOriginX:projectRightOriginX,&lt;br /&gt;
            projectLeftTopX:project.x,&lt;br /&gt;
            projectRightTopX:project.x + project.btnW,&lt;br /&gt;
            projectLeftDir:1,&lt;br /&gt;
            projectRightDir:-1,&lt;br /&gt;
            buttonWidth:layout.buttonWidth,&lt;br /&gt;
            segments:layout.segments.map(function (segment) {&lt;br /&gt;
                return {&lt;br /&gt;
                    key:segment.key,&lt;br /&gt;
                    x:segment.x,&lt;br /&gt;
                    y:segment.y,&lt;br /&gt;
                    btnW:segment.btnW,&lt;br /&gt;
                    pixelWidth:segment.pixelWidth,&lt;br /&gt;
                    slant:segment.slant&lt;br /&gt;
                };&lt;br /&gt;
            }),&lt;br /&gt;
            shellScale:frame.getShellScale()&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createHit(item, index, segment, nav, redraw) {&lt;br /&gt;
        var hit;&lt;br /&gt;
        if (item.pillar) {&lt;br /&gt;
            hit = document.createElement(&#039;span&#039;);&lt;br /&gt;
            hit.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
            hit.setAttribute(&#039;role&#039;, &#039;presentation&#039;);&lt;br /&gt;
            hit.className = &#039;portal-frame-hit portal-category-pillar-hit&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            hit = document.createElement(&#039;a&#039;);&lt;br /&gt;
            hit.href = buildPath(item.title);&lt;br /&gt;
            hit.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
            hit.className = &#039;portal-frame-hit portal-cat-anchor&#039;;&lt;br /&gt;
            hit.setAttribute(&#039;data-category-key&#039;, item.key);&lt;br /&gt;
            hit.addEventListener(&#039;mouseenter&#039;, function () {&lt;br /&gt;
                nav.__frameHoverIndex = index;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
            hit.addEventListener(&#039;mouseleave&#039;, function () {&lt;br /&gt;
                if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
            hit.addEventListener(&#039;focus&#039;, function () {&lt;br /&gt;
                nav.__frameHoverIndex = index;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
            hit.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
                if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        hit.style.left = segment.x + &#039;px&#039;;&lt;br /&gt;
        hit.style.top = segment.y + &#039;px&#039;;&lt;br /&gt;
        hit.style.width = segment.pixelWidth + &#039;px&#039;;&lt;br /&gt;
        hit.style.height = segment.pixelHeight + &#039;px&#039;;&lt;br /&gt;
        hit.appendChild(document.createElement(&#039;span&#039;));&lt;br /&gt;
        hit.lastChild.className = &#039;portal-frame-sr-label&#039;;&lt;br /&gt;
        hit.lastChild.textContent = item.label;&lt;br /&gt;
        return hit;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderMount(mount) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var width;&lt;br /&gt;
        var layout;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var inner;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var hitLayer;&lt;br /&gt;
        var redraw;&lt;br /&gt;
&lt;br /&gt;
        if (!mount || !frame) return false;&lt;br /&gt;
        frame.setShellScale(frame.getShellScale());&lt;br /&gt;
        width = frame.readRenderWidth(mount);&lt;br /&gt;
        if (!width) {&lt;br /&gt;
            if (!mount.__categoryNavRetry) {&lt;br /&gt;
                mount.__categoryNavRetry = window.setTimeout(function () {&lt;br /&gt;
                    mount.__categoryNavRetry = 0;&lt;br /&gt;
                    renderMount(mount);&lt;br /&gt;
                }, 60);&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        layout = calculateLayout(width);&lt;br /&gt;
        if (&lt;br /&gt;
            mount.getAttribute(&#039;data-category-nav-width&#039;) === String(width) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-category-nav-scale&#039;) === String(frame.getShellScale()) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.querySelector(&#039;.portal-category-canvas&#039;)&lt;br /&gt;
        ) {&lt;br /&gt;
            publishSeams(layout);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-frame-canvas-nav&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, &#039;Main categories&#039;);&lt;br /&gt;
        nav.__frameHoverIndex = -1;&lt;br /&gt;
&lt;br /&gt;
        inner = document.createElement(&#039;div&#039;);&lt;br /&gt;
        inner.className = &#039;portal-frame-counter-inner portal-category-counter-inner&#039;;&lt;br /&gt;
        inner.style.width = width + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        canvas = frame.createCanvas(&#039;portal-frame-canvas portal-category-canvas&#039;, width, layout.height);&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-width&#039;, String(width));&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-height&#039;, String(layout.height));&lt;br /&gt;
&lt;br /&gt;
        hitLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        hitLayer.className = &#039;portal-frame-hit-layer&#039;;&lt;br /&gt;
        hitLayer.style.width = width + &#039;px&#039;;&lt;br /&gt;
        hitLayer.style.height = layout.height + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        redraw = function () {&lt;br /&gt;
            draw(canvas, layout, nav.__frameHoverIndex);&lt;br /&gt;
        };&lt;br /&gt;
        ITEMS.forEach(function (item, index) {&lt;br /&gt;
            hitLayer.appendChild(createHit(item, index, layout.segments[index], nav, redraw));&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        inner.appendChild(canvas);&lt;br /&gt;
        inner.appendChild(hitLayer);&lt;br /&gt;
        nav.appendChild(inner);&lt;br /&gt;
        clearNode(mount);&lt;br /&gt;
        mount.appendChild(nav);&lt;br /&gt;
        frame.applyCounterScale([mount, nav], inner, layout.height);&lt;br /&gt;
        redraw();&lt;br /&gt;
        publishSeams(layout);&lt;br /&gt;
&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-rendered&#039;, &#039;canvas&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-width&#039;, String(width));&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-scale&#039;, String(frame.getShellScale()));&lt;br /&gt;
&lt;br /&gt;
        if (window.ResizeObserver &amp;amp;&amp;amp; !mount.__categoryNavObserver) {&lt;br /&gt;
            mount.__categoryNavObserver = new window.ResizeObserver(function () {&lt;br /&gt;
                schedule(mount);&lt;br /&gt;
            });&lt;br /&gt;
            mount.__categoryNavObserver.observe(mount);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var mounts = [];&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;)) mounts.push(scope);&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;), function (mount) {&lt;br /&gt;
            if (mounts.indexOf(mount) === -1) mounts.push(mount);&lt;br /&gt;
        });&lt;br /&gt;
        mounts.forEach(function (mount) {&lt;br /&gt;
            if (renderMount(mount)) rendered += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { renderAll(document); });&lt;br /&gt;
    } else {&lt;br /&gt;
        renderAll(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (api()) api().onScale(function () { schedule(document); });&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            schedule(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () { schedule(document); });&lt;br /&gt;
&lt;br /&gt;
    window.CategoryNav = {&lt;br /&gt;
        version:&#039;20260717-canvas-pixel-001&#039;,&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderAll:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
    window.CLBI = window.CLBI || {};&lt;br /&gt;
    window.CLBI.categoryNav = {&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4689</id>
		<title>미디어위키:BottomGuideNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4689"/>
		<updated>2026-07-17T09:49:32Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-fixed / js/BottomGuideNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
BottomGuideNav&lt;br /&gt;
대문 하단 「길라잡이」 canvas 픽셀 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var COMPONENT = &#039;category-guide-nav&#039;;&lt;br /&gt;
    var retryTimer = 0;&lt;br /&gt;
    var geometryWaitTimer = 0;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var observer = null;&lt;br /&gt;
    var manifestoIntroPromise = null;&lt;br /&gt;
    var MANIFESTO_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var MANIFESTO_INTRO_VERSION = &#039;20260713-divider-equal-gap-005&#039;;&lt;br /&gt;
    var GUIDE_DUMMY_ITEMS = [&lt;br /&gt;
        { key:&#039;start&#039;, label:&#039;처음 오셨다면&#039; },&lt;br /&gt;
        { key:&#039;world&#039;, label:&#039;세계관&#039; },&lt;br /&gt;
        { key:&#039;era&#039;, label:&#039;시대&#039; },&lt;br /&gt;
        { key:&#039;people&#039;, label:&#039;인물&#039; },&lt;br /&gt;
        { key:&#039;anecdote&#039;, label:&#039;에넥도트&#039; },&lt;br /&gt;
        { key:&#039;participate&#039;, label:&#039;참여 안내&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function api() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function revealManifesto(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        var portal = scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;) || document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var copy;&lt;br /&gt;
&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-pending&#039;);&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-active&#039;);&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-prepared&#039;);&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-running&#039;);&lt;br /&gt;
        title = portal.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        copy = portal.querySelector(&#039;.main-manifesto-copy&#039;);&lt;br /&gt;
        if (title) {&lt;br /&gt;
            title.style.opacity = &#039;1&#039;;&lt;br /&gt;
            title.style.borderBottom = &#039;0&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (copy) copy.style.visibility = &#039;visible&#039;;&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line&#039;), function (line) {&lt;br /&gt;
            line.style.height = &#039;26px&#039;;&lt;br /&gt;
            line.style.maxHeight = &#039;26px&#039;;&lt;br /&gt;
            line.style.opacity = &#039;1&#039;;&lt;br /&gt;
            line.style.overflow = &#039;visible&#039;;&lt;br /&gt;
            line.style.clipPath = &#039;inset(0 0 0 0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line-inner&#039;), function (inner) {&lt;br /&gt;
            inner.style.opacity = &#039;1&#039;;&lt;br /&gt;
            inner.style.transform = &#039;translateY(0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifestoIntro(root) {&lt;br /&gt;
        if (&lt;br /&gt;
            window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
            window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
            typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(window.ManifestoIntro.init(root || document)).catch(function () {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    return false;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                revealManifesto(root);&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (manifestoIntroPromise) return manifestoIntroPromise;&lt;br /&gt;
&lt;br /&gt;
        manifestoIntroPromise = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                if (!ok) {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    manifestoIntroPromise = null;&lt;br /&gt;
                }&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            script.src = &#039;/index.php?title=&#039; + encodeURIComponent(&#039;MediaWiki:ManifestoIntro.js&#039;) +&lt;br /&gt;
                &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&amp;amp;v=&#039; + encodeURIComponent(MANIFESTO_INTRO_VERSION) +&lt;br /&gt;
                &#039;&amp;amp;_manifestoFresh=&#039; + encodeURIComponent(String(Date.now()));&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-manifesto-intro-script&#039;, &#039;1&#039;);&lt;br /&gt;
            script.onload = function () {&lt;br /&gt;
                if (&lt;br /&gt;
                    window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
                    window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
                    typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    Promise.resolve(window.ManifestoIntro.init(root || document))&lt;br /&gt;
                        .then(function () { settle(true); })&lt;br /&gt;
                        .catch(function () { settle(false); });&lt;br /&gt;
                } else {&lt;br /&gt;
                    settle(false);&lt;br /&gt;
                }&lt;br /&gt;
            };&lt;br /&gt;
            script.onerror = function () { settle(false); };&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, MANIFESTO_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
        return manifestoIntroPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyPanel(portal) {&lt;br /&gt;
        var panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        var manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                else topMount.parentNode.appendChild(panel);&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        return { panel:panel, well:well };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBodyScreenHidden(screen, hidden) {&lt;br /&gt;
        if (!screen) return;&lt;br /&gt;
        screen.hidden = !!hidden;&lt;br /&gt;
        screen.setAttribute(&#039;aria-hidden&#039;, hidden ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        screen.classList.toggle(&#039;is-main-body-screen-hidden&#039;, !!hidden);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateGuideButtonState(portal, active) {&lt;br /&gt;
        var guideActive = active === &#039;guide&#039;;&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var target = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-anchor&#039;), function (anchor) {&lt;br /&gt;
            anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            anchor.setAttribute(&#039;data-main-body-target&#039;, target);&lt;br /&gt;
            anchor.removeAttribute(&#039;aria-current&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-label&#039;), function (text) {&lt;br /&gt;
            text.textContent = label;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-nav&#039;), function (nav) {&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureGuideDummyMenu(guide) {&lt;br /&gt;
        var menu;&lt;br /&gt;
        if (!guide) return null;&lt;br /&gt;
        menu = guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
        if (menu) return menu;&lt;br /&gt;
&lt;br /&gt;
        menu = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        menu.className = &#039;main-guide-menu&#039;;&lt;br /&gt;
        menu.setAttribute(&#039;data-guide-dummy-menu&#039;, &#039;1&#039;);&lt;br /&gt;
        menu.setAttribute(&#039;aria-label&#039;, &#039;길라잡이 항목 예시&#039;);&lt;br /&gt;
        GUIDE_DUMMY_ITEMS.forEach(function (item, index) {&lt;br /&gt;
            var wrapper = document.createElement(&#039;div&#039;);&lt;br /&gt;
            var button = document.createElement(&#039;button&#039;);&lt;br /&gt;
            var number = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var arrow = document.createElement(&#039;span&#039;);&lt;br /&gt;
            wrapper.className = &#039;main-guide-menu-item&#039;;&lt;br /&gt;
            wrapper.style.setProperty(&#039;--guide-menu-order&#039;, String(index));&lt;br /&gt;
            button.type = &#039;button&#039;;&lt;br /&gt;
            button.className = &#039;main-guide-menu-button&#039;;&lt;br /&gt;
            button.setAttribute(&#039;data-guide-dummy-button&#039;, item.key);&lt;br /&gt;
            button.setAttribute(&#039;aria-label&#039;, item.label + &#039; (더미 버튼)&#039;);&lt;br /&gt;
            number.className = &#039;main-guide-menu-number&#039;;&lt;br /&gt;
            number.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            number.textContent = String(index + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
            label.className = &#039;main-guide-menu-label&#039;;&lt;br /&gt;
            label.textContent = item.label;&lt;br /&gt;
            arrow.className = &#039;main-guide-menu-arrow&#039;;&lt;br /&gt;
            arrow.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            arrow.textContent = &#039;›&#039;;&lt;br /&gt;
            button.appendChild(number);&lt;br /&gt;
            button.appendChild(label);&lt;br /&gt;
            button.appendChild(arrow);&lt;br /&gt;
            button.addEventListener(&#039;click&#039;, function (event) { event.preventDefault(); });&lt;br /&gt;
            wrapper.appendChild(button);&lt;br /&gt;
            menu.appendChild(wrapper);&lt;br /&gt;
        });&lt;br /&gt;
        guide.insertBefore(menu, guide.firstChild);&lt;br /&gt;
        return menu;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function restartGuideMenuAnimation(guide) {&lt;br /&gt;
        if (!guide || !guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;)) return;&lt;br /&gt;
        guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        void guide.offsetWidth;&lt;br /&gt;
        guide.classList.add(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyScreens(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var manifesto = layout.well.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
        var guide = layout.well.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;);&lt;br /&gt;
        var panels;&lt;br /&gt;
        var active = layout.well.getAttribute(&#039;data-active-main-body-screen&#039;) ||&lt;br /&gt;
            portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (manifesto) {&lt;br /&gt;
            manifesto.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            manifesto.setAttribute(&#039;data-main-body-screen&#039;, &#039;manifesto&#039;);&lt;br /&gt;
            if (!manifesto.id) manifesto.id = &#039;main-manifesto-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (!guide) {&lt;br /&gt;
            guide = document.createElement(&#039;section&#039;);&lt;br /&gt;
            guide.className = &#039;main-body-screen main-guide-screen&#039;;&lt;br /&gt;
            guide.setAttribute(&#039;data-main-body-screen&#039;, &#039;guide&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;data-main-guide-screen-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;aria-label&#039;, &#039;길라잡이&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;role&#039;, &#039;region&#039;);&lt;br /&gt;
            guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
            layout.well.appendChild(guide);&lt;br /&gt;
        } else {&lt;br /&gt;
            guide.classList.add(&#039;main-body-screen&#039;, &#039;main-guide-screen&#039;);&lt;br /&gt;
            if (!guide.id) guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        ensureGuideDummyMenu(guide);&lt;br /&gt;
        panels = Array.prototype.slice.call(&lt;br /&gt;
            layout.well.querySelectorAll(&#039;.main-body-screen[data-main-body-screen]&#039;)&lt;br /&gt;
        );&lt;br /&gt;
        if (active !== &#039;guide&#039;) active = &#039;manifesto&#039;;&lt;br /&gt;
        if (!panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === active;&lt;br /&gt;
        })) active = &#039;manifesto&#039;;&lt;br /&gt;
        panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(panel, panel.getAttribute(&#039;data-main-body-screen&#039;) !== active);&lt;br /&gt;
        });&lt;br /&gt;
        layout.well.setAttribute(&#039;data-main-body-screen-controller-ready&#039;, &#039;1&#039;);&lt;br /&gt;
        layout.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; !guide.classList.contains(&#039;is-guide-menu-entering&#039;)) {&lt;br /&gt;
            restartGuideMenuAnimation(guide);&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            well:layout.well,&lt;br /&gt;
            panels:panels,&lt;br /&gt;
            manifesto:manifesto,&lt;br /&gt;
            guide:guide,&lt;br /&gt;
            active:active&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBodyScreen(portal, screenName) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var previous;&lt;br /&gt;
        var active;&lt;br /&gt;
        var event;&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        screenName = screenName === &#039;guide&#039; ? &#039;guide&#039; : &#039;manifesto&#039;;&lt;br /&gt;
        if (!screens.panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === screenName;&lt;br /&gt;
        })) return false;&lt;br /&gt;
        previous = screens.active;&lt;br /&gt;
        active = screenName;&lt;br /&gt;
        screens.panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(panel, panel.getAttribute(&#039;data-main-body-screen&#039;) !== active);&lt;br /&gt;
        });&lt;br /&gt;
        screens.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; previous !== active) restartGuideMenuAnimation(screens.guide);&lt;br /&gt;
        else if (active !== &#039;guide&#039; &amp;amp;&amp;amp; screens.guide) screens.guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        render(portal);&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            try {&lt;br /&gt;
                event = new CustomEvent(&#039;mainbodyscreenchange&#039;, {&lt;br /&gt;
                    bubbles:true,&lt;br /&gt;
                    detail:{ screen:active, previous:previous }&lt;br /&gt;
                });&lt;br /&gt;
                portal.dispatchEvent(event);&lt;br /&gt;
            } catch (error) {}&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function toggleBodyScreen(portal) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        return activateBodyScreen(portal, screens.active === &#039;guide&#039; ? &#039;manifesto&#039; : &#039;guide&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleGuideScreenCapture(event) {&lt;br /&gt;
        var target;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var portal;&lt;br /&gt;
        if (!event || event.__mainGuideScreenHandled) return;&lt;br /&gt;
        if (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;&lt;br /&gt;
        target = event.target;&lt;br /&gt;
        anchor = target &amp;amp;&amp;amp; target.closest&lt;br /&gt;
            ? target.closest(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)&lt;br /&gt;
            : null;&lt;br /&gt;
        if (!anchor) return;&lt;br /&gt;
        portal = anchor.closest ? anchor.closest(&#039;.main-portal&#039;) : null;&lt;br /&gt;
        if (!portal || !toggleBodyScreen(portal)) return;&lt;br /&gt;
        event.__mainGuideScreenHandled = true;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) event.stopImmediatePropagation();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (window.BottomGuideNavScreenCaptureHandler) {&lt;br /&gt;
        document.removeEventListener(&#039;click&#039;, window.BottomGuideNavScreenCaptureHandler, true);&lt;br /&gt;
    }&lt;br /&gt;
    window.BottomGuideNavScreenCaptureHandler = handleGuideScreenCapture;&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleGuideScreenCapture, true);&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
        ensureBodyScreens(portal);&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
            mount.setAttribute(&#039;data-bottom-guide-generated&#039;, &#039;1&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        if (mount.parentNode !== layout.panel) layout.panel.appendChild(mount);&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function guideButton(seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var step = Math.floor(d.BUTTON_EDGE_BOTTOM / frame.FRAME.SLANT_RISE);&lt;br /&gt;
        var left = seams.settingsLeftOriginX + step;&lt;br /&gt;
        var right = seams.corpRightOriginX - step;&lt;br /&gt;
        return {&lt;br /&gt;
            x:Math.round(left),&lt;br /&gt;
            y:d.BUTTON_EDGE_TOP,&lt;br /&gt;
            btnW:Math.max(4, Math.round(right - left)),&lt;br /&gt;
            pixelHeight:d.BUTTON_TOTAL,&lt;br /&gt;
            slant:&#039;both&#039;&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function draw(canvas, button, label, hover) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        var image = frame.createImage(ctx, canvas.width, canvas.height, &#039;BG&#039;);&lt;br /&gt;
        var grid;&lt;br /&gt;
        var mask;&lt;br /&gt;
        var center;&lt;br /&gt;
&lt;br /&gt;
        frame.paintStandardFrame(image, canvas.width, canvas.height);&lt;br /&gt;
        grid = frame.buildReverseButtonPixels(&lt;br /&gt;
            button.btnW,&lt;br /&gt;
            frame.FRAME.BUTTON_FACE,&lt;br /&gt;
            button.slant,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        frame.paintGrid(image, canvas.width, canvas.height, grid, button.x, button.y, true);&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
        mask = frame.buttonMask(&lt;br /&gt;
            button.btnW,&lt;br /&gt;
            frame.FRAME.BUTTON_FACE - 1 - Math.floor(frame.FRAME.BUTTON_FACE / 2),&lt;br /&gt;
            button.slant,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        center = button.x + Math.round((mask.left + mask.right) / 2);&lt;br /&gt;
        frame.drawLabel(&lt;br /&gt;
            ctx,&lt;br /&gt;
            label,&lt;br /&gt;
            center,&lt;br /&gt;
            Math.round((frame.DIMENSIONS.BUTTON_SURFACE_TOP + frame.DIMENSIONS.BUTTON_SURFACE_BOTTOM) / 2) + 1,&lt;br /&gt;
            hover,&lt;br /&gt;
            canvas.width &amp;lt;= 820 ? 11 : 12&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(portal) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var seams;&lt;br /&gt;
        var mount;&lt;br /&gt;
        var width;&lt;br /&gt;
        var guideActive;&lt;br /&gt;
        var label;&lt;br /&gt;
        var targetScreen;&lt;br /&gt;
        var href;&lt;br /&gt;
        var button;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var inner;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var hitLayer;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var hiddenLabel;&lt;br /&gt;
        var redraw;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !frame.FRAME || typeof frame.readRenderWidth !== &#039;function&#039;) {&lt;br /&gt;
            window.clearTimeout(geometryWaitTimer);&lt;br /&gt;
            geometryWaitTimer = window.setTimeout(function () {&lt;br /&gt;
                geometryWaitTimer = 0;&lt;br /&gt;
                render(portal);&lt;br /&gt;
            }, 50);&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        seams = frame.seams;&lt;br /&gt;
        if (!seams || !seams.width) return false;&lt;br /&gt;
        mount = ensureMount(portal);&lt;br /&gt;
        width = seams.width || frame.readRenderWidth(mount);&lt;br /&gt;
        if (!width) return false;&lt;br /&gt;
&lt;br /&gt;
        guideActive = portal.getAttribute(&#039;data-main-body-screen&#039;) === &#039;guide&#039;;&lt;br /&gt;
        label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        targetScreen = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
        button = guideButton(seams);&lt;br /&gt;
        if (&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-width&#039;) === String(width) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-label&#039;) === label &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-scale&#039;) === String(frame.getShellScale()) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.querySelector(&#039;.portal-guide-canvas&#039;)&lt;br /&gt;
        ) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-guide-nav portal-frame-canvas-nav is-continuous-weld&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        nav.__frameHover = false;&lt;br /&gt;
&lt;br /&gt;
        inner = document.createElement(&#039;div&#039;);&lt;br /&gt;
        inner.className = &#039;portal-frame-counter-inner portal-guide-counter-inner&#039;;&lt;br /&gt;
        inner.style.width = width + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        canvas = frame.createCanvas(&#039;portal-frame-canvas portal-guide-canvas&#039;, width, frame.DIMENSIONS.ROW_HEIGHT);&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-width&#039;, String(width));&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-height&#039;, String(frame.DIMENSIONS.ROW_HEIGHT));&lt;br /&gt;
&lt;br /&gt;
        hitLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        hitLayer.className = &#039;portal-frame-hit-layer&#039;;&lt;br /&gt;
        hitLayer.style.width = width + &#039;px&#039;;&lt;br /&gt;
        hitLayer.style.height = frame.DIMENSIONS.ROW_HEIGHT + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        anchor = document.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className = &#039;portal-frame-hit portal-guide-anchor&#039;;&lt;br /&gt;
        anchor.href = href;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        anchor.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, &#039;guide&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-main-body-target&#039;, targetScreen);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-controls&#039;, &#039;main-manifesto-screen main-guide-screen&#039;);&lt;br /&gt;
        anchor.style.left = button.x + &#039;px&#039;;&lt;br /&gt;
        anchor.style.top = button.y + &#039;px&#039;;&lt;br /&gt;
        anchor.style.width = (button.btnW + 2) + &#039;px&#039;;&lt;br /&gt;
        anchor.style.height = button.pixelHeight + &#039;px&#039;;&lt;br /&gt;
        hiddenLabel = document.createElement(&#039;span&#039;);&lt;br /&gt;
        hiddenLabel.className = &#039;portal-frame-sr-label portal-guide-label&#039;;&lt;br /&gt;
        hiddenLabel.textContent = label;&lt;br /&gt;
        anchor.appendChild(hiddenLabel);&lt;br /&gt;
&lt;br /&gt;
        redraw = function () { draw(canvas, button, label, nav.__frameHover); };&lt;br /&gt;
        anchor.addEventListener(&#039;mouseenter&#039;, function () {&lt;br /&gt;
            nav.__frameHover = true;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;mouseleave&#039;, function () {&lt;br /&gt;
            nav.__frameHover = false;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;focus&#039;, function () {&lt;br /&gt;
            nav.__frameHover = true;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
            nav.__frameHover = false;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            if (event &amp;amp;&amp;amp; (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) return;&lt;br /&gt;
            if (toggleBodyScreen(portal) &amp;amp;&amp;amp; event) event.preventDefault();&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        hitLayer.appendChild(anchor);&lt;br /&gt;
        inner.appendChild(canvas);&lt;br /&gt;
        inner.appendChild(hitLayer);&lt;br /&gt;
        nav.appendChild(inner);&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        frame.applyCounterScale(&lt;br /&gt;
            [mount, nav],&lt;br /&gt;
            inner,&lt;br /&gt;
            frame.DIMENSIONS.ROW_HEIGHT&lt;br /&gt;
        );&lt;br /&gt;
        redraw();&lt;br /&gt;
&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-rendered&#039;, &#039;canvas&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-width&#039;, String(width));&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-label&#039;, label);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-scale&#039;, String(frame.getShellScale()));&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findPortal(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, attempt) {&lt;br /&gt;
        window.clearTimeout(retryTimer);&lt;br /&gt;
        retryTimer = window.setTimeout(function () {&lt;br /&gt;
            var portal = findPortal(root);&lt;br /&gt;
            if (!portal) return;&lt;br /&gt;
            if (!render(portal) &amp;amp;&amp;amp; attempt &amp;lt; 20) {&lt;br /&gt;
                schedule(root, attempt + 1);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (observer) observer.disconnect();&lt;br /&gt;
            if (window.ResizeObserver) {&lt;br /&gt;
                observer = new ResizeObserver(function () {&lt;br /&gt;
                    window.clearTimeout(resizeTimer);&lt;br /&gt;
                    resizeTimer = window.setTimeout(function () { render(portal); }, 60);&lt;br /&gt;
                });&lt;br /&gt;
                observer.observe(portal);&lt;br /&gt;
                var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
                var guideMount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
                if (topMount) observer.observe(topMount);&lt;br /&gt;
                if (guideMount) observer.observe(guideMount);&lt;br /&gt;
            }&lt;br /&gt;
        }, attempt ? 60 : 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot(root) {&lt;br /&gt;
        var scope = root || document;&lt;br /&gt;
        schedule(scope, 0);&lt;br /&gt;
        loadManifestoIntro(scope);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    revealManifesto(document);&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { boot(document); }, { once:true });&lt;br /&gt;
    } else {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }&lt;br /&gt;
    window.addEventListener(&#039;load&#039;, function () { boot(document); }, { once:true });&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, function () { boot(document); });&lt;br /&gt;
    if (api()) {&lt;br /&gt;
        api().onSeams(function () { schedule(document, 0); });&lt;br /&gt;
        api().onScale(function () { schedule(document, 0); });&lt;br /&gt;
    }&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            boot($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BottomGuideNav = {&lt;br /&gt;
        version:&#039;20260717-canvas-pixel-001&#039;,&lt;br /&gt;
        render:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? render(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        activate:function (screenName) {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? activateBodyScreen(portal, screenName) : false;&lt;br /&gt;
        },&lt;br /&gt;
        toggle:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? toggleBodyScreen(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        status:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            var mount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
            return {&lt;br /&gt;
                loaded:true,&lt;br /&gt;
                portal:!!portal,&lt;br /&gt;
                mount:!!mount,&lt;br /&gt;
                rendered:!!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-canvas&#039;)),&lt;br /&gt;
                generated:!!(mount &amp;amp;&amp;amp; mount.getAttribute(&#039;data-bottom-guide-generated&#039;)),&lt;br /&gt;
                panel:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;)),&lt;br /&gt;
                well:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-well&#039;)),&lt;br /&gt;
                insidePanel:!!(mount &amp;amp;&amp;amp; mount.parentNode &amp;amp;&amp;amp; mount.parentNode.classList.contains(&#039;main-body-panel&#039;)),&lt;br /&gt;
                welded:!!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-nav.is-continuous-weld&#039;)),&lt;br /&gt;
                activeScreen:portal ? portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039; : &#039;&#039;,&lt;br /&gt;
                guideScreen:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;)),&lt;br /&gt;
                width:mount ? mount.getAttribute(&#039;data-bottom-guide-width&#039;) || &#039;&#039; : &#039;&#039;&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4688</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4688"/>
		<updated>2026-07-17T09:49:31Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-fixed / css/MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:42px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
FrameGeometry.js가 공유한 설정 왼쪽 seam과 기업 및 공동체 오른쪽 seam을&lt;br /&gt;
같은 정수 좌표계에서 연장한다. 상단 프레임 하단 9px 안으로 겹쳐 접합하며,&lt;br /&gt;
표시 높이는 공통 ROW_HEIGHT 42px만 사용한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-outline {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:10%;&lt;br /&gt;
    right:auto;&lt;br /&gt;
    width:80%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 외곽 테두리를 포함한 42px 정수 단면을 사용한다. */&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단·하단 총 42px:&lt;br /&gt;
외곽1 + 프레임면7 + 우물2 + 버튼(테두리1 + 표면20 + 테두리1) + 우물2 + 프레임면7 + 외곽1&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
사선 프레임 단일 정수 좌표계 / 20260717&lt;br /&gt;
FrameGeometry.js의 1+7 / 2 / 1+20+1 / 2 / 7+1 정수 단면을 그대로 표시한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav,&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-highlight,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:focus .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame,&lt;br /&gt;
.main-portal .portal-section-frame,&lt;br /&gt;
.main-portal .portal-category-pillar,&lt;br /&gt;
.main-portal .portal-section-button-geometry,&lt;br /&gt;
.main-portal .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-hit {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 길라잡이도 같은 42px 정수 단면과 반대 사선을 사용한다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    stroke:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* main-body-panel의 1px 좌우 외곽선을 보정해 상단 seam 좌표폭과 일치시킨다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    left:-1px !important;&lt;br /&gt;
    right:-1px !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
}&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 사선 프레임 canvas 픽셀 렌더링 / 20260717&lt;br /&gt;
SVG polygon 표시 계층을 대체한다. shell scale은 안쪽 레이어에서 역보정한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-frame-counter-host {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    margin-right:0 !important;&lt;br /&gt;
    margin-left:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-canvas-nav {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-counter-inner {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    transform-origin:top left !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-canvas {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    image-rendering:pixelated;&lt;br /&gt;
    image-rendering:crisp-edges;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-hit-layer {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-hit {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    outline:0 !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-hit:focus-visible {&lt;br /&gt;
    outline:1px solid #e2e2e2 !important;&lt;br /&gt;
    outline-offset:-2px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-hit {&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    cursor:default !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-sr-label {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    width:1px !important;&lt;br /&gt;
    height:1px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    margin:-1px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    clip:rect(0, 0, 0, 0) !important;&lt;br /&gt;
    white-space:nowrap !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-canvas,&lt;br /&gt;
.main-portal .portal-section-canvas,&lt;br /&gt;
.main-portal .portal-guide-canvas {&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after,&lt;br /&gt;
.main-portal .portal-category-svg,&lt;br /&gt;
.main-portal .portal-section-svg,&lt;br /&gt;
.main-portal .portal-guide-svg {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryPillar.js&amp;diff=4687</id>
		<title>미디어위키:CategoryPillar.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryPillar.js&amp;diff=4687"/>
		<updated>2026-07-17T09:42:44Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-canvas-atomic-v3-20260717 / js/CategoryPillar.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* portal-frame canvas v3 — 2026-07-17 */&lt;br /&gt;
/* =========================================&lt;br /&gt;
CategoryPillar&lt;br /&gt;
canvas CategoryNav 내부 프로젝트 기둥 호환 모듈&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var BUILD_TAG = &#039;portal-frame-canvas-v3-20260717&#039;;&lt;br /&gt;
    window.PortalFrameModules = window.PortalFrameModules || {};&lt;br /&gt;
    window.PortalFrameModules.CategoryPillar = BUILD_TAG;&lt;br /&gt;
&lt;br /&gt;
    var REQUIRED_CATEGORY_VERSION = BUILD_TAG;&lt;br /&gt;
    var warned = false;&lt;br /&gt;
&lt;br /&gt;
    function categoryReady() {&lt;br /&gt;
        var ready = window.CategoryNav &amp;amp;&amp;amp;&lt;br /&gt;
            window.CategoryNav.version === REQUIRED_CATEGORY_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
            typeof window.CategoryNav.renderAll === &#039;function&#039;;&lt;br /&gt;
        if (!ready &amp;amp;&amp;amp; !warned &amp;amp;&amp;amp; window.console &amp;amp;&amp;amp; typeof window.console.error === &#039;function&#039;) {&lt;br /&gt;
            warned = true;&lt;br /&gt;
            window.console.error(&lt;br /&gt;
                &#039;[CategoryNav 구버전 감지: CategoryPillar.js와 MediaWiki:CategoryNav.js를 같은 배포 세트로 업데이트하세요] &#039; +&lt;br /&gt;
                &#039;필요 버전=&#039; + REQUIRED_CATEGORY_VERSION&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
        if (ready) warned = false;&lt;br /&gt;
        return ready;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        if (!categoryReady()) return 0;&lt;br /&gt;
        return scope.querySelectorAll(&lt;br /&gt;
            &#039;.main-portal [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-canvas&#039;&lt;br /&gt;
        ).length;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.CategoryPillar = {&lt;br /&gt;
        version:BUILD_TAG,&lt;br /&gt;
        buildTag:BUILD_TAG,&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderAll:renderAll,&lt;br /&gt;
        render:function () { return renderAll(document) &amp;gt; 0; }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            renderAll(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4686</id>
		<title>미디어위키:CategoryNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4686"/>
		<updated>2026-07-17T09:42:43Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-canvas-atomic-v3-20260717 / js/CategoryNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* portal-frame canvas v3 — 2026-07-17 */&lt;br /&gt;
/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - CategoryNav&lt;br /&gt;
대문 카테고리 canvas 픽셀 네비 생성기&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var BUILD_TAG = &#039;portal-frame-canvas-v3-20260717&#039;;&lt;br /&gt;
    window.PortalFrameModules = window.PortalFrameModules || {};&lt;br /&gt;
    window.PortalFrameModules.CategoryNav = BUILD_TAG;&lt;br /&gt;
&lt;br /&gt;
    var ITEMS = [&lt;br /&gt;
        { key:&#039;era&#039;, label:&#039;시대&#039;, title:&#039;시대&#039;, slant:&#039;right&#039; },&lt;br /&gt;
        { key:&#039;settings&#039;, label:&#039;설정&#039;, title:&#039;설정&#039;, slant:&#039;right&#039; },&lt;br /&gt;
        { key:&#039;project&#039;, label:&#039;프로젝트&#039;, title:&#039;프로젝트:소개&#039;, slant:&#039;both&#039;, pillar:true },&lt;br /&gt;
        { key:&#039;corporations&#039;, label:&#039;기업 및 공동체&#039;, title:&#039;기업_및_공동체&#039;, slant:&#039;left&#039; },&lt;br /&gt;
        { key:&#039;military&#039;, label:&#039;군, 정치집단&#039;, title:&#039;군_정치집단&#039;, slant:&#039;left&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
&lt;br /&gt;
    var EXPECTED_FRAME_API = &#039;20260717-canvas-api-003&#039;;&lt;br /&gt;
    var REQUIRED_FRAME_FUNCTIONS = [&#039;setShellScale&#039;, &#039;getShellScale&#039;, &#039;readRenderWidth&#039;, &#039;createCanvas&#039;, &#039;applyCounterScale&#039;, &#039;createImage&#039;, &#039;paintStandardFrame&#039;, &#039;buildButtonPixels&#039;, &#039;paintGrid&#039;, &#039;setPixel&#039;, &#039;buttonMask&#039;, &#039;drawLabel&#039;, &#039;setSeams&#039;, &#039;onScale&#039;];&lt;br /&gt;
    var REQUIRED_FRAME_OBJECTS = [&#039;FRAME&#039;, &#039;DIMENSIONS&#039;];&lt;br /&gt;
    var frameWarningSignature = &#039;&#039;;&lt;br /&gt;
    var frameListenersBound = false;&lt;br /&gt;
&lt;br /&gt;
    function reportFrameContract(frame, missing) {&lt;br /&gt;
        var version = frame &amp;amp;&amp;amp; frame.apiVersion ? frame.apiVersion : &#039;없음&#039;;&lt;br /&gt;
        var build = frame &amp;amp;&amp;amp; frame.buildTag ? frame.buildTag : &#039;없음&#039;;&lt;br /&gt;
        var signature = build + &#039;|&#039; + version + &#039;|&#039; + missing.join(&#039;|&#039;);&lt;br /&gt;
        if (signature === frameWarningSignature) return;&lt;br /&gt;
        frameWarningSignature = signature;&lt;br /&gt;
        if (window.console &amp;amp;&amp;amp; typeof window.console.error === &#039;function&#039;) {&lt;br /&gt;
            window.console.error(&lt;br /&gt;
                &#039;[FrameGeometry 구버전 감지: CategoryNav.js와 MediaWiki:FrameGeometry.js를 같은 배포 세트로 업데이트하세요] &#039; +&lt;br /&gt;
                &#039;필요 빌드=&#039; + BUILD_TAG + &#039;, 현재 빌드=&#039; + build +&lt;br /&gt;
                &#039;, 필요 API=&#039; + EXPECTED_FRAME_API + &#039;, 현재 API=&#039; + version +&lt;br /&gt;
                &#039;, 누락/불일치=&#039; + (missing.length ? missing.join(&#039;, &#039;) : &#039;apiVersion&#039;)&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function api() {&lt;br /&gt;
        var frame = window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
        var missing = [];&lt;br /&gt;
        if (!frame) {&lt;br /&gt;
            reportFrameContract(null, [&#039;window.CLBI.frame&#039;]);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        if (frame.buildTag !== BUILD_TAG) missing.push(&#039;buildTag&#039;);&lt;br /&gt;
        if (frame.apiVersion !== EXPECTED_FRAME_API) missing.push(&#039;apiVersion&#039;);&lt;br /&gt;
        REQUIRED_FRAME_OBJECTS.forEach(function (name) {&lt;br /&gt;
            if (!frame[name] || typeof frame[name] !== &#039;object&#039;) missing.push(name);&lt;br /&gt;
        });&lt;br /&gt;
        REQUIRED_FRAME_FUNCTIONS.forEach(function (name) {&lt;br /&gt;
            if (typeof frame[name] !== &#039;function&#039;) missing.push(name + &#039;()&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        if (missing.length) {&lt;br /&gt;
            reportFrameContract(frame, missing);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        frameWarningSignature = &#039;&#039;;&lt;br /&gt;
        return frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) return window.buildWikiPath(title);&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearNode(node) {&lt;br /&gt;
        while (node &amp;amp;&amp;amp; node.firstChild) node.removeChild(node.firstChild);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateLayout(width) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        if (!frame) return null;&lt;br /&gt;
        var f = frame.FRAME;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var contentX = d.WELL_TOP;&lt;br /&gt;
        var contentWidth = Math.max(ITEMS.length * 8, width - contentX * 2);&lt;br /&gt;
        var btnW = Math.max(4, Math.floor((contentWidth - f.GAP * 4) / ITEMS.length));&lt;br /&gt;
        var segments = [];&lt;br /&gt;
        var xoff = contentX;&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; ITEMS.length; i++) {&lt;br /&gt;
            segments.push({&lt;br /&gt;
                index:i,&lt;br /&gt;
                key:ITEMS[i].key,&lt;br /&gt;
                label:ITEMS[i].label,&lt;br /&gt;
                slant:ITEMS[i].slant,&lt;br /&gt;
                x:xoff,&lt;br /&gt;
                y:d.BUTTON_EDGE_TOP,&lt;br /&gt;
                btnW:btnW,&lt;br /&gt;
                pixelWidth:btnW + 2,&lt;br /&gt;
                pixelHeight:d.BUTTON_TOTAL&lt;br /&gt;
            });&lt;br /&gt;
            xoff += btnW + f.GAP;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            width:width,&lt;br /&gt;
            height:d.ROW_HEIGHT,&lt;br /&gt;
            contentX:contentX,&lt;br /&gt;
            contentWidth:contentWidth,&lt;br /&gt;
            buttonWidth:btnW,&lt;br /&gt;
            usedRight:xoff - f.GAP,&lt;br /&gt;
            segments:segments&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function paintProjectPillar(image, width, height, segment) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        if (!frame || !image || !segment) return null;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE);&lt;br /&gt;
        var leftTop = segment.x - topStep;&lt;br /&gt;
        var rightTop = segment.x + segment.btnW + topStep;&lt;br /&gt;
        var y;&lt;br /&gt;
        var x;&lt;br /&gt;
&lt;br /&gt;
        for (y = 0; y &amp;lt; height; y++) {&lt;br /&gt;
            var step = Math.floor(y / frame.FRAME.SLANT_RISE);&lt;br /&gt;
            var left = leftTop + step;&lt;br /&gt;
            var right = rightTop - step;&lt;br /&gt;
            for (x = left; x &amp;lt;= right; x++) frame.setPixel(image, width, height, x, y, &#039;S&#039;);&lt;br /&gt;
            frame.setPixel(image, width, height, left, y, &#039;D&#039;);&lt;br /&gt;
            frame.setPixel(image, width, height, right, y, &#039;H&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            leftTop:leftTop,&lt;br /&gt;
            rightTop:rightTop,&lt;br /&gt;
            leftAtButton:segment.x,&lt;br /&gt;
            rightAtButton:segment.x + segment.btnW&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function labelCenter(segment) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        if (!frame || !segment) return 0;&lt;br /&gt;
        var ys = Math.floor(frame.FRAME.BUTTON_FACE / 2);&lt;br /&gt;
        var mask = frame.buttonMask(segment.btnW, ys, segment.slant, 0);&lt;br /&gt;
        return segment.x + Math.round((mask.left + mask.right) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function draw(canvas, layout, hoverIndex) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        if (!frame || !canvas || !layout) return false;&lt;br /&gt;
        var ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        var image = frame.createImage(ctx, layout.width, layout.height, &#039;BG&#039;);&lt;br /&gt;
        var projectGeometry = null;&lt;br /&gt;
&lt;br /&gt;
        frame.paintStandardFrame(image, layout.width, layout.height);&lt;br /&gt;
        layout.segments.forEach(function (segment) {&lt;br /&gt;
            var grid = frame.buildButtonPixels(&lt;br /&gt;
                segment.btnW,&lt;br /&gt;
                frame.FRAME.BUTTON_FACE,&lt;br /&gt;
                segment.slant,&lt;br /&gt;
                0&lt;br /&gt;
            );&lt;br /&gt;
            frame.paintGrid(&lt;br /&gt;
                image,&lt;br /&gt;
                layout.width,&lt;br /&gt;
                layout.height,&lt;br /&gt;
                grid,&lt;br /&gt;
                segment.x,&lt;br /&gt;
                segment.y,&lt;br /&gt;
                true&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        projectGeometry = paintProjectPillar(&lt;br /&gt;
            image,&lt;br /&gt;
            layout.width,&lt;br /&gt;
            layout.height,&lt;br /&gt;
            layout.segments[2]&lt;br /&gt;
        );&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
&lt;br /&gt;
        layout.segments.forEach(function (segment, index) {&lt;br /&gt;
            var center = index === 2&lt;br /&gt;
                ? Math.round((projectGeometry.leftAtButton + projectGeometry.rightAtButton) / 2)&lt;br /&gt;
                : labelCenter(segment);&lt;br /&gt;
            frame.drawLabel(&lt;br /&gt;
                ctx,&lt;br /&gt;
                segment.label,&lt;br /&gt;
                center,&lt;br /&gt;
                Math.round((frame.DIMENSIONS.BUTTON_SURFACE_TOP + frame.DIMENSIONS.BUTTON_SURFACE_BOTTOM) / 2) + 1,&lt;br /&gt;
                hoverIndex === index &amp;amp;&amp;amp; index !== 2,&lt;br /&gt;
                layout.width &amp;lt;= 820 ? 11 : 12&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
        canvas.__projectGeometry = projectGeometry;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function publishSeams(layout) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        if (!frame || !layout) return false;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var settings = layout.segments[1];&lt;br /&gt;
        var project = layout.segments[2];&lt;br /&gt;
        var corporations = layout.segments[3];&lt;br /&gt;
        var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE);&lt;br /&gt;
        var settingsLeftOriginX = settings.x - topStep;&lt;br /&gt;
        var corpRightOriginX = corporations.x + corporations.btnW + topStep;&lt;br /&gt;
        var projectLeftOriginX = project.x - topStep;&lt;br /&gt;
        var projectRightOriginX = project.x + project.btnW + topStep;&lt;br /&gt;
        var seamStep = Math.floor(d.BOTTOM_START_ABS_Y / frame.FRAME.SLANT_RISE);&lt;br /&gt;
&lt;br /&gt;
        frame.setSeams({&lt;br /&gt;
            width:layout.width,&lt;br /&gt;
            rowHeight:d.ROW_HEIGHT,&lt;br /&gt;
            buttonTop:d.BUTTON_EDGE_TOP,&lt;br /&gt;
            buttonBottom:d.BUTTON_EDGE_BOTTOM,&lt;br /&gt;
            overlap:d.OVERLAP,&lt;br /&gt;
            bottomStartAbsY:d.BOTTOM_START_ABS_Y,&lt;br /&gt;
            phase:d.BOTTOM_START_ABS_Y % frame.FRAME.SLANT_RISE,&lt;br /&gt;
            settingsLeftOriginX:settingsLeftOriginX,&lt;br /&gt;
            settingsLeftTopX:settings.x,&lt;br /&gt;
            settingsLeftSeamX:settingsLeftOriginX + seamStep,&lt;br /&gt;
            settingsLeftDir:1,&lt;br /&gt;
            corpRightOriginX:corpRightOriginX,&lt;br /&gt;
            corpRightTopX:corporations.x + corporations.btnW,&lt;br /&gt;
            corpRightSeamX:corpRightOriginX - seamStep,&lt;br /&gt;
            corpRightDir:-1,&lt;br /&gt;
            projectLeftOriginX:projectLeftOriginX,&lt;br /&gt;
            projectRightOriginX:projectRightOriginX,&lt;br /&gt;
            projectLeftTopX:project.x,&lt;br /&gt;
            projectRightTopX:project.x + project.btnW,&lt;br /&gt;
            projectLeftDir:1,&lt;br /&gt;
            projectRightDir:-1,&lt;br /&gt;
            buttonWidth:layout.buttonWidth,&lt;br /&gt;
            segments:layout.segments.map(function (segment) {&lt;br /&gt;
                return {&lt;br /&gt;
                    key:segment.key,&lt;br /&gt;
                    x:segment.x,&lt;br /&gt;
                    y:segment.y,&lt;br /&gt;
                    btnW:segment.btnW,&lt;br /&gt;
                    pixelWidth:segment.pixelWidth,&lt;br /&gt;
                    slant:segment.slant&lt;br /&gt;
                };&lt;br /&gt;
            }),&lt;br /&gt;
            shellScale:frame.getShellScale()&lt;br /&gt;
        });&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createHit(item, index, segment, nav, redraw) {&lt;br /&gt;
        var hit;&lt;br /&gt;
        if (item.pillar) {&lt;br /&gt;
            hit = document.createElement(&#039;span&#039;);&lt;br /&gt;
            hit.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
            hit.setAttribute(&#039;role&#039;, &#039;presentation&#039;);&lt;br /&gt;
            hit.className = &#039;portal-frame-hit portal-category-pillar-hit&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            hit = document.createElement(&#039;a&#039;);&lt;br /&gt;
            hit.href = buildPath(item.title);&lt;br /&gt;
            hit.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
            hit.className = &#039;portal-frame-hit portal-cat-anchor&#039;;&lt;br /&gt;
            hit.setAttribute(&#039;data-category-key&#039;, item.key);&lt;br /&gt;
            hit.addEventListener(&#039;mouseenter&#039;, function () {&lt;br /&gt;
                nav.__frameHoverIndex = index;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
            hit.addEventListener(&#039;mouseleave&#039;, function () {&lt;br /&gt;
                if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
            hit.addEventListener(&#039;focus&#039;, function () {&lt;br /&gt;
                nav.__frameHoverIndex = index;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
            hit.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
                if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        hit.style.left = segment.x + &#039;px&#039;;&lt;br /&gt;
        hit.style.top = segment.y + &#039;px&#039;;&lt;br /&gt;
        hit.style.width = segment.pixelWidth + &#039;px&#039;;&lt;br /&gt;
        hit.style.height = segment.pixelHeight + &#039;px&#039;;&lt;br /&gt;
        hit.appendChild(document.createElement(&#039;span&#039;));&lt;br /&gt;
        hit.lastChild.className = &#039;portal-frame-sr-label&#039;;&lt;br /&gt;
        hit.lastChild.textContent = item.label;&lt;br /&gt;
        return hit;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderMount(mount) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var width;&lt;br /&gt;
        var layout;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var inner;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var hitLayer;&lt;br /&gt;
        var redraw;&lt;br /&gt;
&lt;br /&gt;
        if (!mount || !frame) return false;&lt;br /&gt;
        frame.setShellScale(frame.getShellScale());&lt;br /&gt;
        width = frame.readRenderWidth(mount);&lt;br /&gt;
        if (!width) {&lt;br /&gt;
            if (!mount.__categoryNavRetry) {&lt;br /&gt;
                mount.__categoryNavRetry = window.setTimeout(function () {&lt;br /&gt;
                    mount.__categoryNavRetry = 0;&lt;br /&gt;
                    renderMount(mount);&lt;br /&gt;
                }, 60);&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        layout = calculateLayout(width);&lt;br /&gt;
        if (!layout) return false;&lt;br /&gt;
        if (&lt;br /&gt;
            mount.getAttribute(&#039;data-category-nav-width&#039;) === String(width) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-category-nav-scale&#039;) === String(frame.getShellScale()) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.querySelector(&#039;.portal-category-canvas&#039;)&lt;br /&gt;
        ) {&lt;br /&gt;
            publishSeams(layout);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-frame-canvas-nav&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, &#039;Main categories&#039;);&lt;br /&gt;
        nav.__frameHoverIndex = -1;&lt;br /&gt;
&lt;br /&gt;
        inner = document.createElement(&#039;div&#039;);&lt;br /&gt;
        inner.className = &#039;portal-frame-counter-inner portal-category-counter-inner&#039;;&lt;br /&gt;
        inner.style.width = width + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        canvas = frame.createCanvas(&#039;portal-frame-canvas portal-category-canvas&#039;, width, layout.height);&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-width&#039;, String(width));&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-height&#039;, String(layout.height));&lt;br /&gt;
&lt;br /&gt;
        hitLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        hitLayer.className = &#039;portal-frame-hit-layer&#039;;&lt;br /&gt;
        hitLayer.style.width = width + &#039;px&#039;;&lt;br /&gt;
        hitLayer.style.height = layout.height + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        redraw = function () {&lt;br /&gt;
            draw(canvas, layout, nav.__frameHoverIndex);&lt;br /&gt;
        };&lt;br /&gt;
        ITEMS.forEach(function (item, index) {&lt;br /&gt;
            hitLayer.appendChild(createHit(item, index, layout.segments[index], nav, redraw));&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        inner.appendChild(canvas);&lt;br /&gt;
        inner.appendChild(hitLayer);&lt;br /&gt;
        nav.appendChild(inner);&lt;br /&gt;
        clearNode(mount);&lt;br /&gt;
        mount.appendChild(nav);&lt;br /&gt;
        frame.applyCounterScale([mount, nav], inner, layout.height);&lt;br /&gt;
        redraw();&lt;br /&gt;
        publishSeams(layout);&lt;br /&gt;
&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-rendered&#039;, &#039;canvas&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-width&#039;, String(width));&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-scale&#039;, String(frame.getShellScale()));&lt;br /&gt;
&lt;br /&gt;
        if (window.ResizeObserver &amp;amp;&amp;amp; !mount.__categoryNavObserver) {&lt;br /&gt;
            mount.__categoryNavObserver = new window.ResizeObserver(function () {&lt;br /&gt;
                schedule(mount);&lt;br /&gt;
            });&lt;br /&gt;
            mount.__categoryNavObserver.observe(mount);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var mounts = [];&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;)) mounts.push(scope);&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;), function (mount) {&lt;br /&gt;
            if (mounts.indexOf(mount) === -1) mounts.push(mount);&lt;br /&gt;
        });&lt;br /&gt;
        mounts.forEach(function (mount) {&lt;br /&gt;
            if (renderMount(mount)) rendered += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function bindFrameListeners() {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        if (!frame || frameListenersBound) return false;&lt;br /&gt;
        frameListenersBound = true;&lt;br /&gt;
        frame.onScale(function () { schedule(document); });&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { renderAll(document); });&lt;br /&gt;
    } else {&lt;br /&gt;
        renderAll(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bindFrameListeners();&lt;br /&gt;
    document.addEventListener(&#039;framegeometryready&#039;, function () {&lt;br /&gt;
        if (bindFrameListeners()) schedule(document);&lt;br /&gt;
    });&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            schedule(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () { schedule(document); });&lt;br /&gt;
&lt;br /&gt;
    window.CategoryNav = {&lt;br /&gt;
        version:BUILD_TAG,&lt;br /&gt;
        buildTag:BUILD_TAG,&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderAll:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
    window.CLBI = window.CLBI || {};&lt;br /&gt;
    window.CLBI.categoryNav = {&lt;br /&gt;
        version:BUILD_TAG,&lt;br /&gt;
        buildTag:BUILD_TAG,&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4685</id>
		<title>미디어위키:BottomGuideNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4685"/>
		<updated>2026-07-17T09:42:43Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-canvas-atomic-v3-20260717 / js/BottomGuideNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* portal-frame canvas v3 — 2026-07-17 */&lt;br /&gt;
/* =========================================&lt;br /&gt;
BottomGuideNav&lt;br /&gt;
대문 하단 「길라잡이」 canvas 픽셀 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var BUILD_TAG = &#039;portal-frame-canvas-v3-20260717&#039;;&lt;br /&gt;
    window.PortalFrameModules = window.PortalFrameModules || {};&lt;br /&gt;
    window.PortalFrameModules.BottomGuideNav = BUILD_TAG;&lt;br /&gt;
&lt;br /&gt;
    var COMPONENT = &#039;category-guide-nav&#039;;&lt;br /&gt;
    var retryTimer = 0;&lt;br /&gt;
    var geometryWaitTimer = 0;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var observer = null;&lt;br /&gt;
    var manifestoIntroPromise = null;&lt;br /&gt;
    var MANIFESTO_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var MANIFESTO_INTRO_VERSION = &#039;20260713-divider-equal-gap-005&#039;;&lt;br /&gt;
    var GUIDE_DUMMY_ITEMS = [&lt;br /&gt;
        { key:&#039;start&#039;, label:&#039;처음 오셨다면&#039; },&lt;br /&gt;
        { key:&#039;world&#039;, label:&#039;세계관&#039; },&lt;br /&gt;
        { key:&#039;era&#039;, label:&#039;시대&#039; },&lt;br /&gt;
        { key:&#039;people&#039;, label:&#039;인물&#039; },&lt;br /&gt;
        { key:&#039;anecdote&#039;, label:&#039;에넥도트&#039; },&lt;br /&gt;
        { key:&#039;participate&#039;, label:&#039;참여 안내&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var EXPECTED_FRAME_API = &#039;20260717-canvas-api-003&#039;;&lt;br /&gt;
    var REQUIRED_FRAME_FUNCTIONS = [&#039;getShellScale&#039;, &#039;readRenderWidth&#039;, &#039;createCanvas&#039;, &#039;applyCounterScale&#039;, &#039;createImage&#039;, &#039;paintStandardFrame&#039;, &#039;buildReverseButtonPixels&#039;, &#039;paintGrid&#039;, &#039;buttonMask&#039;, &#039;drawLabel&#039;, &#039;onSeams&#039;, &#039;onScale&#039;];&lt;br /&gt;
    var REQUIRED_FRAME_OBJECTS = [&#039;FRAME&#039;, &#039;DIMENSIONS&#039;];&lt;br /&gt;
    var frameWarningSignature = &#039;&#039;;&lt;br /&gt;
    var frameListenersBound = false;&lt;br /&gt;
&lt;br /&gt;
    function reportFrameContract(frame, missing) {&lt;br /&gt;
        var version = frame &amp;amp;&amp;amp; frame.apiVersion ? frame.apiVersion : &#039;없음&#039;;&lt;br /&gt;
        var build = frame &amp;amp;&amp;amp; frame.buildTag ? frame.buildTag : &#039;없음&#039;;&lt;br /&gt;
        var signature = build + &#039;|&#039; + version + &#039;|&#039; + missing.join(&#039;|&#039;);&lt;br /&gt;
        if (signature === frameWarningSignature) return;&lt;br /&gt;
        frameWarningSignature = signature;&lt;br /&gt;
        if (window.console &amp;amp;&amp;amp; typeof window.console.error === &#039;function&#039;) {&lt;br /&gt;
            window.console.error(&lt;br /&gt;
                &#039;[FrameGeometry 구버전 감지: BottomGuideNav.js와 MediaWiki:FrameGeometry.js를 같은 배포 세트로 업데이트하세요] &#039; +&lt;br /&gt;
                &#039;필요 빌드=&#039; + BUILD_TAG + &#039;, 현재 빌드=&#039; + build +&lt;br /&gt;
                &#039;, 필요 API=&#039; + EXPECTED_FRAME_API + &#039;, 현재 API=&#039; + version +&lt;br /&gt;
                &#039;, 누락/불일치=&#039; + (missing.length ? missing.join(&#039;, &#039;) : &#039;apiVersion&#039;)&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function api() {&lt;br /&gt;
        var frame = window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
        var missing = [];&lt;br /&gt;
        if (!frame) {&lt;br /&gt;
            reportFrameContract(null, [&#039;window.CLBI.frame&#039;]);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        if (frame.buildTag !== BUILD_TAG) missing.push(&#039;buildTag&#039;);&lt;br /&gt;
        if (frame.apiVersion !== EXPECTED_FRAME_API) missing.push(&#039;apiVersion&#039;);&lt;br /&gt;
        REQUIRED_FRAME_OBJECTS.forEach(function (name) {&lt;br /&gt;
            if (!frame[name] || typeof frame[name] !== &#039;object&#039;) missing.push(name);&lt;br /&gt;
        });&lt;br /&gt;
        REQUIRED_FRAME_FUNCTIONS.forEach(function (name) {&lt;br /&gt;
            if (typeof frame[name] !== &#039;function&#039;) missing.push(name + &#039;()&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        if (missing.length) {&lt;br /&gt;
            reportFrameContract(frame, missing);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        frameWarningSignature = &#039;&#039;;&lt;br /&gt;
        return frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function revealManifesto(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        var portal = scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;) || document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var copy;&lt;br /&gt;
&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-pending&#039;);&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-active&#039;);&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-prepared&#039;);&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-running&#039;);&lt;br /&gt;
        title = portal.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        copy = portal.querySelector(&#039;.main-manifesto-copy&#039;);&lt;br /&gt;
        if (title) {&lt;br /&gt;
            title.style.opacity = &#039;1&#039;;&lt;br /&gt;
            title.style.borderBottom = &#039;0&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (copy) copy.style.visibility = &#039;visible&#039;;&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line&#039;), function (line) {&lt;br /&gt;
            line.style.height = &#039;26px&#039;;&lt;br /&gt;
            line.style.maxHeight = &#039;26px&#039;;&lt;br /&gt;
            line.style.opacity = &#039;1&#039;;&lt;br /&gt;
            line.style.overflow = &#039;visible&#039;;&lt;br /&gt;
            line.style.clipPath = &#039;inset(0 0 0 0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line-inner&#039;), function (inner) {&lt;br /&gt;
            inner.style.opacity = &#039;1&#039;;&lt;br /&gt;
            inner.style.transform = &#039;translateY(0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifestoIntro(root) {&lt;br /&gt;
        if (&lt;br /&gt;
            window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
            window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
            typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(window.ManifestoIntro.init(root || document)).catch(function () {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    return false;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                revealManifesto(root);&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (manifestoIntroPromise) return manifestoIntroPromise;&lt;br /&gt;
&lt;br /&gt;
        manifestoIntroPromise = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                if (!ok) {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    manifestoIntroPromise = null;&lt;br /&gt;
                }&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            script.src = &#039;/index.php?title=&#039; + encodeURIComponent(&#039;MediaWiki:ManifestoIntro.js&#039;) +&lt;br /&gt;
                &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&amp;amp;v=&#039; + encodeURIComponent(MANIFESTO_INTRO_VERSION) +&lt;br /&gt;
                &#039;&amp;amp;_manifestoFresh=&#039; + encodeURIComponent(String(Date.now()));&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-manifesto-intro-script&#039;, &#039;1&#039;);&lt;br /&gt;
            script.onload = function () {&lt;br /&gt;
                if (&lt;br /&gt;
                    window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
                    window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
                    typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    Promise.resolve(window.ManifestoIntro.init(root || document))&lt;br /&gt;
                        .then(function () { settle(true); })&lt;br /&gt;
                        .catch(function () { settle(false); });&lt;br /&gt;
                } else {&lt;br /&gt;
                    settle(false);&lt;br /&gt;
                }&lt;br /&gt;
            };&lt;br /&gt;
            script.onerror = function () { settle(false); };&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, MANIFESTO_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
        return manifestoIntroPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyPanel(portal) {&lt;br /&gt;
        var panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        var manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                else topMount.parentNode.appendChild(panel);&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        return { panel:panel, well:well };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBodyScreenHidden(screen, hidden) {&lt;br /&gt;
        if (!screen) return;&lt;br /&gt;
        screen.hidden = !!hidden;&lt;br /&gt;
        screen.setAttribute(&#039;aria-hidden&#039;, hidden ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        screen.classList.toggle(&#039;is-main-body-screen-hidden&#039;, !!hidden);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateGuideButtonState(portal, active) {&lt;br /&gt;
        var guideActive = active === &#039;guide&#039;;&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var target = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-anchor&#039;), function (anchor) {&lt;br /&gt;
            anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            anchor.setAttribute(&#039;data-main-body-target&#039;, target);&lt;br /&gt;
            anchor.removeAttribute(&#039;aria-current&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-label&#039;), function (text) {&lt;br /&gt;
            text.textContent = label;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-nav&#039;), function (nav) {&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureGuideDummyMenu(guide) {&lt;br /&gt;
        var menu;&lt;br /&gt;
        if (!guide) return null;&lt;br /&gt;
        menu = guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
        if (menu) return menu;&lt;br /&gt;
&lt;br /&gt;
        menu = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        menu.className = &#039;main-guide-menu&#039;;&lt;br /&gt;
        menu.setAttribute(&#039;data-guide-dummy-menu&#039;, &#039;1&#039;);&lt;br /&gt;
        menu.setAttribute(&#039;aria-label&#039;, &#039;길라잡이 항목 예시&#039;);&lt;br /&gt;
        GUIDE_DUMMY_ITEMS.forEach(function (item, index) {&lt;br /&gt;
            var wrapper = document.createElement(&#039;div&#039;);&lt;br /&gt;
            var button = document.createElement(&#039;button&#039;);&lt;br /&gt;
            var number = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var arrow = document.createElement(&#039;span&#039;);&lt;br /&gt;
            wrapper.className = &#039;main-guide-menu-item&#039;;&lt;br /&gt;
            wrapper.style.setProperty(&#039;--guide-menu-order&#039;, String(index));&lt;br /&gt;
            button.type = &#039;button&#039;;&lt;br /&gt;
            button.className = &#039;main-guide-menu-button&#039;;&lt;br /&gt;
            button.setAttribute(&#039;data-guide-dummy-button&#039;, item.key);&lt;br /&gt;
            button.setAttribute(&#039;aria-label&#039;, item.label + &#039; (더미 버튼)&#039;);&lt;br /&gt;
            number.className = &#039;main-guide-menu-number&#039;;&lt;br /&gt;
            number.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            number.textContent = String(index + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
            label.className = &#039;main-guide-menu-label&#039;;&lt;br /&gt;
            label.textContent = item.label;&lt;br /&gt;
            arrow.className = &#039;main-guide-menu-arrow&#039;;&lt;br /&gt;
            arrow.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            arrow.textContent = &#039;›&#039;;&lt;br /&gt;
            button.appendChild(number);&lt;br /&gt;
            button.appendChild(label);&lt;br /&gt;
            button.appendChild(arrow);&lt;br /&gt;
            button.addEventListener(&#039;click&#039;, function (event) { event.preventDefault(); });&lt;br /&gt;
            wrapper.appendChild(button);&lt;br /&gt;
            menu.appendChild(wrapper);&lt;br /&gt;
        });&lt;br /&gt;
        guide.insertBefore(menu, guide.firstChild);&lt;br /&gt;
        return menu;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function restartGuideMenuAnimation(guide) {&lt;br /&gt;
        if (!guide || !guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;)) return;&lt;br /&gt;
        guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        void guide.offsetWidth;&lt;br /&gt;
        guide.classList.add(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyScreens(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var manifesto = layout.well.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
        var guide = layout.well.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;);&lt;br /&gt;
        var panels;&lt;br /&gt;
        var active = layout.well.getAttribute(&#039;data-active-main-body-screen&#039;) ||&lt;br /&gt;
            portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (manifesto) {&lt;br /&gt;
            manifesto.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            manifesto.setAttribute(&#039;data-main-body-screen&#039;, &#039;manifesto&#039;);&lt;br /&gt;
            if (!manifesto.id) manifesto.id = &#039;main-manifesto-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (!guide) {&lt;br /&gt;
            guide = document.createElement(&#039;section&#039;);&lt;br /&gt;
            guide.className = &#039;main-body-screen main-guide-screen&#039;;&lt;br /&gt;
            guide.setAttribute(&#039;data-main-body-screen&#039;, &#039;guide&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;data-main-guide-screen-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;aria-label&#039;, &#039;길라잡이&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;role&#039;, &#039;region&#039;);&lt;br /&gt;
            guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
            layout.well.appendChild(guide);&lt;br /&gt;
        } else {&lt;br /&gt;
            guide.classList.add(&#039;main-body-screen&#039;, &#039;main-guide-screen&#039;);&lt;br /&gt;
            if (!guide.id) guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        ensureGuideDummyMenu(guide);&lt;br /&gt;
        panels = Array.prototype.slice.call(&lt;br /&gt;
            layout.well.querySelectorAll(&#039;.main-body-screen[data-main-body-screen]&#039;)&lt;br /&gt;
        );&lt;br /&gt;
        if (active !== &#039;guide&#039;) active = &#039;manifesto&#039;;&lt;br /&gt;
        if (!panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === active;&lt;br /&gt;
        })) active = &#039;manifesto&#039;;&lt;br /&gt;
        panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(panel, panel.getAttribute(&#039;data-main-body-screen&#039;) !== active);&lt;br /&gt;
        });&lt;br /&gt;
        layout.well.setAttribute(&#039;data-main-body-screen-controller-ready&#039;, &#039;1&#039;);&lt;br /&gt;
        layout.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; !guide.classList.contains(&#039;is-guide-menu-entering&#039;)) {&lt;br /&gt;
            restartGuideMenuAnimation(guide);&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            well:layout.well,&lt;br /&gt;
            panels:panels,&lt;br /&gt;
            manifesto:manifesto,&lt;br /&gt;
            guide:guide,&lt;br /&gt;
            active:active&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBodyScreen(portal, screenName) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var previous;&lt;br /&gt;
        var active;&lt;br /&gt;
        var event;&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        screenName = screenName === &#039;guide&#039; ? &#039;guide&#039; : &#039;manifesto&#039;;&lt;br /&gt;
        if (!screens.panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === screenName;&lt;br /&gt;
        })) return false;&lt;br /&gt;
        previous = screens.active;&lt;br /&gt;
        active = screenName;&lt;br /&gt;
        screens.panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(panel, panel.getAttribute(&#039;data-main-body-screen&#039;) !== active);&lt;br /&gt;
        });&lt;br /&gt;
        screens.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; previous !== active) restartGuideMenuAnimation(screens.guide);&lt;br /&gt;
        else if (active !== &#039;guide&#039; &amp;amp;&amp;amp; screens.guide) screens.guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        render(portal);&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            try {&lt;br /&gt;
                event = new CustomEvent(&#039;mainbodyscreenchange&#039;, {&lt;br /&gt;
                    bubbles:true,&lt;br /&gt;
                    detail:{ screen:active, previous:previous }&lt;br /&gt;
                });&lt;br /&gt;
                portal.dispatchEvent(event);&lt;br /&gt;
            } catch (error) {}&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function toggleBodyScreen(portal) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        return activateBodyScreen(portal, screens.active === &#039;guide&#039; ? &#039;manifesto&#039; : &#039;guide&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleGuideScreenCapture(event) {&lt;br /&gt;
        var target;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var portal;&lt;br /&gt;
        if (!event || event.__mainGuideScreenHandled) return;&lt;br /&gt;
        if (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;&lt;br /&gt;
        target = event.target;&lt;br /&gt;
        anchor = target &amp;amp;&amp;amp; target.closest&lt;br /&gt;
            ? target.closest(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)&lt;br /&gt;
            : null;&lt;br /&gt;
        if (!anchor) return;&lt;br /&gt;
        portal = anchor.closest ? anchor.closest(&#039;.main-portal&#039;) : null;&lt;br /&gt;
        if (!portal || !toggleBodyScreen(portal)) return;&lt;br /&gt;
        event.__mainGuideScreenHandled = true;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) event.stopImmediatePropagation();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (window.BottomGuideNavScreenCaptureHandler) {&lt;br /&gt;
        document.removeEventListener(&#039;click&#039;, window.BottomGuideNavScreenCaptureHandler, true);&lt;br /&gt;
    }&lt;br /&gt;
    window.BottomGuideNavScreenCaptureHandler = handleGuideScreenCapture;&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleGuideScreenCapture, true);&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
        ensureBodyScreens(portal);&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
            mount.setAttribute(&#039;data-bottom-guide-generated&#039;, &#039;1&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        if (mount.parentNode !== layout.panel) layout.panel.appendChild(mount);&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function guideButton(seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        if (!frame || !seams) return null;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var step = Math.floor(d.BUTTON_EDGE_BOTTOM / frame.FRAME.SLANT_RISE);&lt;br /&gt;
        var left = seams.settingsLeftOriginX + step;&lt;br /&gt;
        var right = seams.corpRightOriginX - step;&lt;br /&gt;
        return {&lt;br /&gt;
            x:Math.round(left),&lt;br /&gt;
            y:d.BUTTON_EDGE_TOP,&lt;br /&gt;
            btnW:Math.max(4, Math.round(right - left)),&lt;br /&gt;
            pixelHeight:d.BUTTON_TOTAL,&lt;br /&gt;
            slant:&#039;both&#039;&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function draw(canvas, button, label, hover) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        if (!frame || !canvas || !button) return false;&lt;br /&gt;
        var ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        var image = frame.createImage(ctx, canvas.width, canvas.height, &#039;BG&#039;);&lt;br /&gt;
        var grid;&lt;br /&gt;
        var mask;&lt;br /&gt;
        var center;&lt;br /&gt;
&lt;br /&gt;
        frame.paintStandardFrame(image, canvas.width, canvas.height);&lt;br /&gt;
        grid = frame.buildReverseButtonPixels(&lt;br /&gt;
            button.btnW,&lt;br /&gt;
            frame.FRAME.BUTTON_FACE,&lt;br /&gt;
            button.slant,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        frame.paintGrid(image, canvas.width, canvas.height, grid, button.x, button.y, true);&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
        mask = frame.buttonMask(&lt;br /&gt;
            button.btnW,&lt;br /&gt;
            frame.FRAME.BUTTON_FACE - 1 - Math.floor(frame.FRAME.BUTTON_FACE / 2),&lt;br /&gt;
            button.slant,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        center = button.x + Math.round((mask.left + mask.right) / 2);&lt;br /&gt;
        frame.drawLabel(&lt;br /&gt;
            ctx,&lt;br /&gt;
            label,&lt;br /&gt;
            center,&lt;br /&gt;
            Math.round((frame.DIMENSIONS.BUTTON_SURFACE_TOP + frame.DIMENSIONS.BUTTON_SURFACE_BOTTOM) / 2) + 1,&lt;br /&gt;
            hover,&lt;br /&gt;
            canvas.width &amp;lt;= 820 ? 11 : 12&lt;br /&gt;
        );&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(portal) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var seams;&lt;br /&gt;
        var mount;&lt;br /&gt;
        var width;&lt;br /&gt;
        var guideActive;&lt;br /&gt;
        var label;&lt;br /&gt;
        var targetScreen;&lt;br /&gt;
        var href;&lt;br /&gt;
        var button;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var inner;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var hitLayer;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var hiddenLabel;&lt;br /&gt;
        var redraw;&lt;br /&gt;
&lt;br /&gt;
        if (!frame) {&lt;br /&gt;
            window.clearTimeout(geometryWaitTimer);&lt;br /&gt;
            geometryWaitTimer = window.setTimeout(function () {&lt;br /&gt;
                geometryWaitTimer = 0;&lt;br /&gt;
                render(portal);&lt;br /&gt;
            }, 50);&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        seams = frame.seams;&lt;br /&gt;
        if (!seams || !seams.width) return false;&lt;br /&gt;
        mount = ensureMount(portal);&lt;br /&gt;
        width = seams.width || frame.readRenderWidth(mount);&lt;br /&gt;
        if (!width) return false;&lt;br /&gt;
&lt;br /&gt;
        guideActive = portal.getAttribute(&#039;data-main-body-screen&#039;) === &#039;guide&#039;;&lt;br /&gt;
        label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        targetScreen = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
        button = guideButton(seams);&lt;br /&gt;
        if (!button) return false;&lt;br /&gt;
        if (&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-width&#039;) === String(width) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-label&#039;) === label &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-scale&#039;) === String(frame.getShellScale()) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.querySelector(&#039;.portal-guide-canvas&#039;)&lt;br /&gt;
        ) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-guide-nav portal-frame-canvas-nav is-continuous-weld&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        nav.__frameHover = false;&lt;br /&gt;
&lt;br /&gt;
        inner = document.createElement(&#039;div&#039;);&lt;br /&gt;
        inner.className = &#039;portal-frame-counter-inner portal-guide-counter-inner&#039;;&lt;br /&gt;
        inner.style.width = width + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        canvas = frame.createCanvas(&#039;portal-frame-canvas portal-guide-canvas&#039;, width, frame.DIMENSIONS.ROW_HEIGHT);&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-width&#039;, String(width));&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-height&#039;, String(frame.DIMENSIONS.ROW_HEIGHT));&lt;br /&gt;
&lt;br /&gt;
        hitLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        hitLayer.className = &#039;portal-frame-hit-layer&#039;;&lt;br /&gt;
        hitLayer.style.width = width + &#039;px&#039;;&lt;br /&gt;
        hitLayer.style.height = frame.DIMENSIONS.ROW_HEIGHT + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        anchor = document.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className = &#039;portal-frame-hit portal-guide-anchor&#039;;&lt;br /&gt;
        anchor.href = href;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        anchor.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, &#039;guide&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-main-body-target&#039;, targetScreen);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-controls&#039;, &#039;main-manifesto-screen main-guide-screen&#039;);&lt;br /&gt;
        anchor.style.left = button.x + &#039;px&#039;;&lt;br /&gt;
        anchor.style.top = button.y + &#039;px&#039;;&lt;br /&gt;
        anchor.style.width = (button.btnW + 2) + &#039;px&#039;;&lt;br /&gt;
        anchor.style.height = button.pixelHeight + &#039;px&#039;;&lt;br /&gt;
        hiddenLabel = document.createElement(&#039;span&#039;);&lt;br /&gt;
        hiddenLabel.className = &#039;portal-frame-sr-label portal-guide-label&#039;;&lt;br /&gt;
        hiddenLabel.textContent = label;&lt;br /&gt;
        anchor.appendChild(hiddenLabel);&lt;br /&gt;
&lt;br /&gt;
        redraw = function () { draw(canvas, button, label, nav.__frameHover); };&lt;br /&gt;
        anchor.addEventListener(&#039;mouseenter&#039;, function () {&lt;br /&gt;
            nav.__frameHover = true;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;mouseleave&#039;, function () {&lt;br /&gt;
            nav.__frameHover = false;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;focus&#039;, function () {&lt;br /&gt;
            nav.__frameHover = true;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
            nav.__frameHover = false;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            if (event &amp;amp;&amp;amp; (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) return;&lt;br /&gt;
            if (toggleBodyScreen(portal) &amp;amp;&amp;amp; event) event.preventDefault();&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        hitLayer.appendChild(anchor);&lt;br /&gt;
        inner.appendChild(canvas);&lt;br /&gt;
        inner.appendChild(hitLayer);&lt;br /&gt;
        nav.appendChild(inner);&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        frame.applyCounterScale(&lt;br /&gt;
            [mount, nav],&lt;br /&gt;
            inner,&lt;br /&gt;
            frame.DIMENSIONS.ROW_HEIGHT&lt;br /&gt;
        );&lt;br /&gt;
        redraw();&lt;br /&gt;
&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-rendered&#039;, &#039;canvas&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-width&#039;, String(width));&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-label&#039;, label);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-scale&#039;, String(frame.getShellScale()));&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findPortal(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, attempt) {&lt;br /&gt;
        window.clearTimeout(retryTimer);&lt;br /&gt;
        retryTimer = window.setTimeout(function () {&lt;br /&gt;
            var portal = findPortal(root);&lt;br /&gt;
            if (!portal) return;&lt;br /&gt;
            if (!render(portal) &amp;amp;&amp;amp; attempt &amp;lt; 20) {&lt;br /&gt;
                schedule(root, attempt + 1);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (observer) observer.disconnect();&lt;br /&gt;
            if (window.ResizeObserver) {&lt;br /&gt;
                observer = new ResizeObserver(function () {&lt;br /&gt;
                    window.clearTimeout(resizeTimer);&lt;br /&gt;
                    resizeTimer = window.setTimeout(function () { render(portal); }, 60);&lt;br /&gt;
                });&lt;br /&gt;
                observer.observe(portal);&lt;br /&gt;
                var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
                var guideMount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
                if (topMount) observer.observe(topMount);&lt;br /&gt;
                if (guideMount) observer.observe(guideMount);&lt;br /&gt;
            }&lt;br /&gt;
        }, attempt ? 60 : 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot(root) {&lt;br /&gt;
        var scope = root || document;&lt;br /&gt;
        schedule(scope, 0);&lt;br /&gt;
        loadManifestoIntro(scope);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function bindFrameListeners() {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        if (!frame || frameListenersBound) return false;&lt;br /&gt;
        frameListenersBound = true;&lt;br /&gt;
        frame.onSeams(function () { schedule(document, 0); });&lt;br /&gt;
        frame.onScale(function () { schedule(document, 0); });&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    revealManifesto(document);&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { boot(document); }, { once:true });&lt;br /&gt;
    } else {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }&lt;br /&gt;
    window.addEventListener(&#039;load&#039;, function () { boot(document); }, { once:true });&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, function () { boot(document); });&lt;br /&gt;
    bindFrameListeners();&lt;br /&gt;
    document.addEventListener(&#039;framegeometryready&#039;, function () {&lt;br /&gt;
        if (bindFrameListeners()) schedule(document, 0);&lt;br /&gt;
    });&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            boot($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BottomGuideNav = {&lt;br /&gt;
        version:BUILD_TAG,&lt;br /&gt;
        buildTag:BUILD_TAG,&lt;br /&gt;
        render:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? render(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        activate:function (screenName) {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? activateBodyScreen(portal, screenName) : false;&lt;br /&gt;
        },&lt;br /&gt;
        toggle:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? toggleBodyScreen(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        status:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            var mount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
            return {&lt;br /&gt;
                loaded:true,&lt;br /&gt;
                portal:!!portal,&lt;br /&gt;
                mount:!!mount,&lt;br /&gt;
                rendered:!!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-canvas&#039;)),&lt;br /&gt;
                generated:!!(mount &amp;amp;&amp;amp; mount.getAttribute(&#039;data-bottom-guide-generated&#039;)),&lt;br /&gt;
                panel:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;)),&lt;br /&gt;
                well:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-well&#039;)),&lt;br /&gt;
                insidePanel:!!(mount &amp;amp;&amp;amp; mount.parentNode &amp;amp;&amp;amp; mount.parentNode.classList.contains(&#039;main-body-panel&#039;)),&lt;br /&gt;
                welded:!!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-nav.is-continuous-weld&#039;)),&lt;br /&gt;
                activeScreen:portal ? portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039; : &#039;&#039;,&lt;br /&gt;
                guideScreen:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;)),&lt;br /&gt;
                width:mount ? mount.getAttribute(&#039;data-bottom-guide-width&#039;) || &#039;&#039; : &#039;&#039;&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4684</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4684"/>
		<updated>2026-07-17T09:42:42Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-canvas-atomic-v3-20260717 / css/MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* portal-frame canvas v3 — 2026-07-17 */&lt;br /&gt;
:root {&lt;br /&gt;
    --portal-frame-css-build:portal-frame-canvas-v3-20260717;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --portal-frame-build:portal-frame-canvas-v3-20260717;&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:42px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
FrameGeometry.js가 공유한 설정 왼쪽 seam과 기업 및 공동체 오른쪽 seam을&lt;br /&gt;
같은 정수 좌표계에서 연장한다. 상단 프레임 하단 9px 안으로 겹쳐 접합하며,&lt;br /&gt;
표시 높이는 공통 ROW_HEIGHT 42px만 사용한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-outline {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:10%;&lt;br /&gt;
    right:auto;&lt;br /&gt;
    width:80%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 외곽 테두리를 포함한 42px 정수 단면을 사용한다. */&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단·하단 총 42px:&lt;br /&gt;
외곽1 + 프레임면7 + 우물2 + 버튼(테두리1 + 표면20 + 테두리1) + 우물2 + 프레임면7 + 외곽1&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
사선 프레임 단일 정수 좌표계 / 20260717&lt;br /&gt;
FrameGeometry.js의 1+7 / 2 / 1+20+1 / 2 / 7+1 정수 단면을 그대로 표시한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav,&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-highlight,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:focus .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame,&lt;br /&gt;
.main-portal .portal-section-frame,&lt;br /&gt;
.main-portal .portal-category-pillar,&lt;br /&gt;
.main-portal .portal-section-button-geometry,&lt;br /&gt;
.main-portal .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-hit {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 길라잡이도 같은 42px 정수 단면과 반대 사선을 사용한다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    stroke:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* main-body-panel의 1px 좌우 외곽선을 보정해 상단 seam 좌표폭과 일치시킨다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    left:-1px !important;&lt;br /&gt;
    right:-1px !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
}&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 사선 프레임 canvas 픽셀 렌더링 / 20260717&lt;br /&gt;
SVG polygon 표시 계층을 대체한다. shell scale은 안쪽 레이어에서 역보정한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-frame-counter-host {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    margin-right:0 !important;&lt;br /&gt;
    margin-left:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-canvas-nav {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-counter-inner {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    transform-origin:top left !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-canvas {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    image-rendering:pixelated;&lt;br /&gt;
    image-rendering:crisp-edges;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-hit-layer {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-hit {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    outline:0 !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-hit:focus-visible {&lt;br /&gt;
    outline:1px solid #e2e2e2 !important;&lt;br /&gt;
    outline-offset:-2px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-hit {&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    cursor:default !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-sr-label {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    width:1px !important;&lt;br /&gt;
    height:1px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    margin:-1px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    clip:rect(0, 0, 0, 0) !important;&lt;br /&gt;
    white-space:nowrap !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-canvas,&lt;br /&gt;
.main-portal .portal-section-canvas,&lt;br /&gt;
.main-portal .portal-guide-canvas {&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after,&lt;br /&gt;
.main-portal .portal-category-svg,&lt;br /&gt;
.main-portal .portal-section-svg,&lt;br /&gt;
.main-portal .portal-guide-svg {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4683</id>
		<title>미디어위키:BottomGuideNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4683"/>
		<updated>2026-07-17T09:27:37Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-canvas-api-contract-fix-20260717 (1) / js/BottomGuideNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
BottomGuideNav&lt;br /&gt;
대문 하단 「길라잡이」 canvas 픽셀 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var COMPONENT = &#039;category-guide-nav&#039;;&lt;br /&gt;
    var retryTimer = 0;&lt;br /&gt;
    var geometryWaitTimer = 0;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var observer = null;&lt;br /&gt;
    var manifestoIntroPromise = null;&lt;br /&gt;
    var MANIFESTO_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var MANIFESTO_INTRO_VERSION = &#039;20260713-divider-equal-gap-005&#039;;&lt;br /&gt;
    var GUIDE_DUMMY_ITEMS = [&lt;br /&gt;
        { key:&#039;start&#039;, label:&#039;처음 오셨다면&#039; },&lt;br /&gt;
        { key:&#039;world&#039;, label:&#039;세계관&#039; },&lt;br /&gt;
        { key:&#039;era&#039;, label:&#039;시대&#039; },&lt;br /&gt;
        { key:&#039;people&#039;, label:&#039;인물&#039; },&lt;br /&gt;
        { key:&#039;anecdote&#039;, label:&#039;에넥도트&#039; },&lt;br /&gt;
        { key:&#039;participate&#039;, label:&#039;참여 안내&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var EXPECTED_FRAME_API = &#039;20260717-canvas-api-002&#039;;&lt;br /&gt;
    var REQUIRED_FRAME_FUNCTIONS = [&#039;getShellScale&#039;, &#039;readRenderWidth&#039;, &#039;createCanvas&#039;, &#039;applyCounterScale&#039;, &#039;createImage&#039;, &#039;paintStandardFrame&#039;, &#039;buildReverseButtonPixels&#039;, &#039;paintGrid&#039;, &#039;buttonMask&#039;, &#039;drawLabel&#039;, &#039;onSeams&#039;, &#039;onScale&#039;];&lt;br /&gt;
    var REQUIRED_FRAME_OBJECTS = [&#039;FRAME&#039;, &#039;DIMENSIONS&#039;];&lt;br /&gt;
    var frameWarningSignature = &#039;&#039;;&lt;br /&gt;
    var frameListenersBound = false;&lt;br /&gt;
&lt;br /&gt;
    function reportFrameContract(frame, missing) {&lt;br /&gt;
        var version = frame &amp;amp;&amp;amp; frame.apiVersion ? frame.apiVersion : &#039;없음&#039;;&lt;br /&gt;
        var signature = version + &#039;|&#039; + missing.join(&#039;|&#039;);&lt;br /&gt;
        if (signature === frameWarningSignature) return;&lt;br /&gt;
        frameWarningSignature = signature;&lt;br /&gt;
        if (window.console &amp;amp;&amp;amp; typeof window.console.error === &#039;function&#039;) {&lt;br /&gt;
            window.console.error(&lt;br /&gt;
                &#039;[FrameGeometry 구버전 감지: BottomGuideNav.js와 MediaWiki:FrameGeometry.js를 같은 배포 세트로 업데이트하세요] &#039; +&lt;br /&gt;
                &#039;필요 API=&#039; + EXPECTED_FRAME_API + &#039;, 현재=&#039; + version +&lt;br /&gt;
                &#039;, 누락/불일치=&#039; + (missing.length ? missing.join(&#039;, &#039;) : &#039;apiVersion&#039;)&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function api() {&lt;br /&gt;
        var frame = window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
        var missing = [];&lt;br /&gt;
        if (!frame) {&lt;br /&gt;
            reportFrameContract(null, [&#039;window.CLBI.frame&#039;]);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        if (frame.apiVersion !== EXPECTED_FRAME_API) missing.push(&#039;apiVersion&#039;);&lt;br /&gt;
        REQUIRED_FRAME_OBJECTS.forEach(function (name) {&lt;br /&gt;
            if (!frame[name] || typeof frame[name] !== &#039;object&#039;) missing.push(name);&lt;br /&gt;
        });&lt;br /&gt;
        REQUIRED_FRAME_FUNCTIONS.forEach(function (name) {&lt;br /&gt;
            if (typeof frame[name] !== &#039;function&#039;) missing.push(name + &#039;()&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        if (missing.length) {&lt;br /&gt;
            reportFrameContract(frame, missing);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        frameWarningSignature = &#039;&#039;;&lt;br /&gt;
        return frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function revealManifesto(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        var portal = scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;) || document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var copy;&lt;br /&gt;
&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-pending&#039;);&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-active&#039;);&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-prepared&#039;);&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-running&#039;);&lt;br /&gt;
        title = portal.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        copy = portal.querySelector(&#039;.main-manifesto-copy&#039;);&lt;br /&gt;
        if (title) {&lt;br /&gt;
            title.style.opacity = &#039;1&#039;;&lt;br /&gt;
            title.style.borderBottom = &#039;0&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (copy) copy.style.visibility = &#039;visible&#039;;&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line&#039;), function (line) {&lt;br /&gt;
            line.style.height = &#039;26px&#039;;&lt;br /&gt;
            line.style.maxHeight = &#039;26px&#039;;&lt;br /&gt;
            line.style.opacity = &#039;1&#039;;&lt;br /&gt;
            line.style.overflow = &#039;visible&#039;;&lt;br /&gt;
            line.style.clipPath = &#039;inset(0 0 0 0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line-inner&#039;), function (inner) {&lt;br /&gt;
            inner.style.opacity = &#039;1&#039;;&lt;br /&gt;
            inner.style.transform = &#039;translateY(0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifestoIntro(root) {&lt;br /&gt;
        if (&lt;br /&gt;
            window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
            window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
            typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(window.ManifestoIntro.init(root || document)).catch(function () {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    return false;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                revealManifesto(root);&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (manifestoIntroPromise) return manifestoIntroPromise;&lt;br /&gt;
&lt;br /&gt;
        manifestoIntroPromise = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                if (!ok) {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    manifestoIntroPromise = null;&lt;br /&gt;
                }&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            script.src = &#039;/index.php?title=&#039; + encodeURIComponent(&#039;MediaWiki:ManifestoIntro.js&#039;) +&lt;br /&gt;
                &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&amp;amp;v=&#039; + encodeURIComponent(MANIFESTO_INTRO_VERSION) +&lt;br /&gt;
                &#039;&amp;amp;_manifestoFresh=&#039; + encodeURIComponent(String(Date.now()));&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-manifesto-intro-script&#039;, &#039;1&#039;);&lt;br /&gt;
            script.onload = function () {&lt;br /&gt;
                if (&lt;br /&gt;
                    window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
                    window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
                    typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    Promise.resolve(window.ManifestoIntro.init(root || document))&lt;br /&gt;
                        .then(function () { settle(true); })&lt;br /&gt;
                        .catch(function () { settle(false); });&lt;br /&gt;
                } else {&lt;br /&gt;
                    settle(false);&lt;br /&gt;
                }&lt;br /&gt;
            };&lt;br /&gt;
            script.onerror = function () { settle(false); };&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, MANIFESTO_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
        return manifestoIntroPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyPanel(portal) {&lt;br /&gt;
        var panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        var manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                else topMount.parentNode.appendChild(panel);&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        return { panel:panel, well:well };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBodyScreenHidden(screen, hidden) {&lt;br /&gt;
        if (!screen) return;&lt;br /&gt;
        screen.hidden = !!hidden;&lt;br /&gt;
        screen.setAttribute(&#039;aria-hidden&#039;, hidden ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        screen.classList.toggle(&#039;is-main-body-screen-hidden&#039;, !!hidden);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateGuideButtonState(portal, active) {&lt;br /&gt;
        var guideActive = active === &#039;guide&#039;;&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var target = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-anchor&#039;), function (anchor) {&lt;br /&gt;
            anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            anchor.setAttribute(&#039;data-main-body-target&#039;, target);&lt;br /&gt;
            anchor.removeAttribute(&#039;aria-current&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-label&#039;), function (text) {&lt;br /&gt;
            text.textContent = label;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-nav&#039;), function (nav) {&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureGuideDummyMenu(guide) {&lt;br /&gt;
        var menu;&lt;br /&gt;
        if (!guide) return null;&lt;br /&gt;
        menu = guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
        if (menu) return menu;&lt;br /&gt;
&lt;br /&gt;
        menu = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        menu.className = &#039;main-guide-menu&#039;;&lt;br /&gt;
        menu.setAttribute(&#039;data-guide-dummy-menu&#039;, &#039;1&#039;);&lt;br /&gt;
        menu.setAttribute(&#039;aria-label&#039;, &#039;길라잡이 항목 예시&#039;);&lt;br /&gt;
        GUIDE_DUMMY_ITEMS.forEach(function (item, index) {&lt;br /&gt;
            var wrapper = document.createElement(&#039;div&#039;);&lt;br /&gt;
            var button = document.createElement(&#039;button&#039;);&lt;br /&gt;
            var number = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var arrow = document.createElement(&#039;span&#039;);&lt;br /&gt;
            wrapper.className = &#039;main-guide-menu-item&#039;;&lt;br /&gt;
            wrapper.style.setProperty(&#039;--guide-menu-order&#039;, String(index));&lt;br /&gt;
            button.type = &#039;button&#039;;&lt;br /&gt;
            button.className = &#039;main-guide-menu-button&#039;;&lt;br /&gt;
            button.setAttribute(&#039;data-guide-dummy-button&#039;, item.key);&lt;br /&gt;
            button.setAttribute(&#039;aria-label&#039;, item.label + &#039; (더미 버튼)&#039;);&lt;br /&gt;
            number.className = &#039;main-guide-menu-number&#039;;&lt;br /&gt;
            number.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            number.textContent = String(index + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
            label.className = &#039;main-guide-menu-label&#039;;&lt;br /&gt;
            label.textContent = item.label;&lt;br /&gt;
            arrow.className = &#039;main-guide-menu-arrow&#039;;&lt;br /&gt;
            arrow.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            arrow.textContent = &#039;›&#039;;&lt;br /&gt;
            button.appendChild(number);&lt;br /&gt;
            button.appendChild(label);&lt;br /&gt;
            button.appendChild(arrow);&lt;br /&gt;
            button.addEventListener(&#039;click&#039;, function (event) { event.preventDefault(); });&lt;br /&gt;
            wrapper.appendChild(button);&lt;br /&gt;
            menu.appendChild(wrapper);&lt;br /&gt;
        });&lt;br /&gt;
        guide.insertBefore(menu, guide.firstChild);&lt;br /&gt;
        return menu;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function restartGuideMenuAnimation(guide) {&lt;br /&gt;
        if (!guide || !guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;)) return;&lt;br /&gt;
        guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        void guide.offsetWidth;&lt;br /&gt;
        guide.classList.add(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyScreens(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var manifesto = layout.well.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
        var guide = layout.well.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;);&lt;br /&gt;
        var panels;&lt;br /&gt;
        var active = layout.well.getAttribute(&#039;data-active-main-body-screen&#039;) ||&lt;br /&gt;
            portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (manifesto) {&lt;br /&gt;
            manifesto.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            manifesto.setAttribute(&#039;data-main-body-screen&#039;, &#039;manifesto&#039;);&lt;br /&gt;
            if (!manifesto.id) manifesto.id = &#039;main-manifesto-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (!guide) {&lt;br /&gt;
            guide = document.createElement(&#039;section&#039;);&lt;br /&gt;
            guide.className = &#039;main-body-screen main-guide-screen&#039;;&lt;br /&gt;
            guide.setAttribute(&#039;data-main-body-screen&#039;, &#039;guide&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;data-main-guide-screen-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;aria-label&#039;, &#039;길라잡이&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;role&#039;, &#039;region&#039;);&lt;br /&gt;
            guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
            layout.well.appendChild(guide);&lt;br /&gt;
        } else {&lt;br /&gt;
            guide.classList.add(&#039;main-body-screen&#039;, &#039;main-guide-screen&#039;);&lt;br /&gt;
            if (!guide.id) guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        ensureGuideDummyMenu(guide);&lt;br /&gt;
        panels = Array.prototype.slice.call(&lt;br /&gt;
            layout.well.querySelectorAll(&#039;.main-body-screen[data-main-body-screen]&#039;)&lt;br /&gt;
        );&lt;br /&gt;
        if (active !== &#039;guide&#039;) active = &#039;manifesto&#039;;&lt;br /&gt;
        if (!panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === active;&lt;br /&gt;
        })) active = &#039;manifesto&#039;;&lt;br /&gt;
        panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(panel, panel.getAttribute(&#039;data-main-body-screen&#039;) !== active);&lt;br /&gt;
        });&lt;br /&gt;
        layout.well.setAttribute(&#039;data-main-body-screen-controller-ready&#039;, &#039;1&#039;);&lt;br /&gt;
        layout.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; !guide.classList.contains(&#039;is-guide-menu-entering&#039;)) {&lt;br /&gt;
            restartGuideMenuAnimation(guide);&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            well:layout.well,&lt;br /&gt;
            panels:panels,&lt;br /&gt;
            manifesto:manifesto,&lt;br /&gt;
            guide:guide,&lt;br /&gt;
            active:active&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBodyScreen(portal, screenName) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var previous;&lt;br /&gt;
        var active;&lt;br /&gt;
        var event;&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        screenName = screenName === &#039;guide&#039; ? &#039;guide&#039; : &#039;manifesto&#039;;&lt;br /&gt;
        if (!screens.panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === screenName;&lt;br /&gt;
        })) return false;&lt;br /&gt;
        previous = screens.active;&lt;br /&gt;
        active = screenName;&lt;br /&gt;
        screens.panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(panel, panel.getAttribute(&#039;data-main-body-screen&#039;) !== active);&lt;br /&gt;
        });&lt;br /&gt;
        screens.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; previous !== active) restartGuideMenuAnimation(screens.guide);&lt;br /&gt;
        else if (active !== &#039;guide&#039; &amp;amp;&amp;amp; screens.guide) screens.guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        render(portal);&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            try {&lt;br /&gt;
                event = new CustomEvent(&#039;mainbodyscreenchange&#039;, {&lt;br /&gt;
                    bubbles:true,&lt;br /&gt;
                    detail:{ screen:active, previous:previous }&lt;br /&gt;
                });&lt;br /&gt;
                portal.dispatchEvent(event);&lt;br /&gt;
            } catch (error) {}&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function toggleBodyScreen(portal) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        return activateBodyScreen(portal, screens.active === &#039;guide&#039; ? &#039;manifesto&#039; : &#039;guide&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleGuideScreenCapture(event) {&lt;br /&gt;
        var target;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var portal;&lt;br /&gt;
        if (!event || event.__mainGuideScreenHandled) return;&lt;br /&gt;
        if (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;&lt;br /&gt;
        target = event.target;&lt;br /&gt;
        anchor = target &amp;amp;&amp;amp; target.closest&lt;br /&gt;
            ? target.closest(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)&lt;br /&gt;
            : null;&lt;br /&gt;
        if (!anchor) return;&lt;br /&gt;
        portal = anchor.closest ? anchor.closest(&#039;.main-portal&#039;) : null;&lt;br /&gt;
        if (!portal || !toggleBodyScreen(portal)) return;&lt;br /&gt;
        event.__mainGuideScreenHandled = true;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) event.stopImmediatePropagation();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (window.BottomGuideNavScreenCaptureHandler) {&lt;br /&gt;
        document.removeEventListener(&#039;click&#039;, window.BottomGuideNavScreenCaptureHandler, true);&lt;br /&gt;
    }&lt;br /&gt;
    window.BottomGuideNavScreenCaptureHandler = handleGuideScreenCapture;&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleGuideScreenCapture, true);&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
        ensureBodyScreens(portal);&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
            mount.setAttribute(&#039;data-bottom-guide-generated&#039;, &#039;1&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        if (mount.parentNode !== layout.panel) layout.panel.appendChild(mount);&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function guideButton(seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var step = Math.floor(d.BUTTON_EDGE_BOTTOM / frame.FRAME.SLANT_RISE);&lt;br /&gt;
        var left = seams.settingsLeftOriginX + step;&lt;br /&gt;
        var right = seams.corpRightOriginX - step;&lt;br /&gt;
        return {&lt;br /&gt;
            x:Math.round(left),&lt;br /&gt;
            y:d.BUTTON_EDGE_TOP,&lt;br /&gt;
            btnW:Math.max(4, Math.round(right - left)),&lt;br /&gt;
            pixelHeight:d.BUTTON_TOTAL,&lt;br /&gt;
            slant:&#039;both&#039;&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function draw(canvas, button, label, hover) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        var image = frame.createImage(ctx, canvas.width, canvas.height, &#039;BG&#039;);&lt;br /&gt;
        var grid;&lt;br /&gt;
        var mask;&lt;br /&gt;
        var center;&lt;br /&gt;
&lt;br /&gt;
        frame.paintStandardFrame(image, canvas.width, canvas.height);&lt;br /&gt;
        grid = frame.buildReverseButtonPixels(&lt;br /&gt;
            button.btnW,&lt;br /&gt;
            frame.FRAME.BUTTON_FACE,&lt;br /&gt;
            button.slant,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        frame.paintGrid(image, canvas.width, canvas.height, grid, button.x, button.y, true);&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
        mask = frame.buttonMask(&lt;br /&gt;
            button.btnW,&lt;br /&gt;
            frame.FRAME.BUTTON_FACE - 1 - Math.floor(frame.FRAME.BUTTON_FACE / 2),&lt;br /&gt;
            button.slant,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        center = button.x + Math.round((mask.left + mask.right) / 2);&lt;br /&gt;
        frame.drawLabel(&lt;br /&gt;
            ctx,&lt;br /&gt;
            label,&lt;br /&gt;
            center,&lt;br /&gt;
            Math.round((frame.DIMENSIONS.BUTTON_SURFACE_TOP + frame.DIMENSIONS.BUTTON_SURFACE_BOTTOM) / 2) + 1,&lt;br /&gt;
            hover,&lt;br /&gt;
            canvas.width &amp;lt;= 820 ? 11 : 12&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(portal) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var seams;&lt;br /&gt;
        var mount;&lt;br /&gt;
        var width;&lt;br /&gt;
        var guideActive;&lt;br /&gt;
        var label;&lt;br /&gt;
        var targetScreen;&lt;br /&gt;
        var href;&lt;br /&gt;
        var button;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var inner;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var hitLayer;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var hiddenLabel;&lt;br /&gt;
        var redraw;&lt;br /&gt;
&lt;br /&gt;
        if (!frame) {&lt;br /&gt;
            window.clearTimeout(geometryWaitTimer);&lt;br /&gt;
            geometryWaitTimer = window.setTimeout(function () {&lt;br /&gt;
                geometryWaitTimer = 0;&lt;br /&gt;
                render(portal);&lt;br /&gt;
            }, 50);&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        seams = frame.seams;&lt;br /&gt;
        if (!seams || !seams.width) return false;&lt;br /&gt;
        mount = ensureMount(portal);&lt;br /&gt;
        width = seams.width || frame.readRenderWidth(mount);&lt;br /&gt;
        if (!width) return false;&lt;br /&gt;
&lt;br /&gt;
        guideActive = portal.getAttribute(&#039;data-main-body-screen&#039;) === &#039;guide&#039;;&lt;br /&gt;
        label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        targetScreen = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
        button = guideButton(seams);&lt;br /&gt;
        if (&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-width&#039;) === String(width) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-label&#039;) === label &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-scale&#039;) === String(frame.getShellScale()) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.querySelector(&#039;.portal-guide-canvas&#039;)&lt;br /&gt;
        ) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-guide-nav portal-frame-canvas-nav is-continuous-weld&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        nav.__frameHover = false;&lt;br /&gt;
&lt;br /&gt;
        inner = document.createElement(&#039;div&#039;);&lt;br /&gt;
        inner.className = &#039;portal-frame-counter-inner portal-guide-counter-inner&#039;;&lt;br /&gt;
        inner.style.width = width + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        canvas = frame.createCanvas(&#039;portal-frame-canvas portal-guide-canvas&#039;, width, frame.DIMENSIONS.ROW_HEIGHT);&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-width&#039;, String(width));&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-height&#039;, String(frame.DIMENSIONS.ROW_HEIGHT));&lt;br /&gt;
&lt;br /&gt;
        hitLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        hitLayer.className = &#039;portal-frame-hit-layer&#039;;&lt;br /&gt;
        hitLayer.style.width = width + &#039;px&#039;;&lt;br /&gt;
        hitLayer.style.height = frame.DIMENSIONS.ROW_HEIGHT + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        anchor = document.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className = &#039;portal-frame-hit portal-guide-anchor&#039;;&lt;br /&gt;
        anchor.href = href;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        anchor.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, &#039;guide&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-main-body-target&#039;, targetScreen);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-controls&#039;, &#039;main-manifesto-screen main-guide-screen&#039;);&lt;br /&gt;
        anchor.style.left = button.x + &#039;px&#039;;&lt;br /&gt;
        anchor.style.top = button.y + &#039;px&#039;;&lt;br /&gt;
        anchor.style.width = (button.btnW + 2) + &#039;px&#039;;&lt;br /&gt;
        anchor.style.height = button.pixelHeight + &#039;px&#039;;&lt;br /&gt;
        hiddenLabel = document.createElement(&#039;span&#039;);&lt;br /&gt;
        hiddenLabel.className = &#039;portal-frame-sr-label portal-guide-label&#039;;&lt;br /&gt;
        hiddenLabel.textContent = label;&lt;br /&gt;
        anchor.appendChild(hiddenLabel);&lt;br /&gt;
&lt;br /&gt;
        redraw = function () { draw(canvas, button, label, nav.__frameHover); };&lt;br /&gt;
        anchor.addEventListener(&#039;mouseenter&#039;, function () {&lt;br /&gt;
            nav.__frameHover = true;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;mouseleave&#039;, function () {&lt;br /&gt;
            nav.__frameHover = false;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;focus&#039;, function () {&lt;br /&gt;
            nav.__frameHover = true;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
            nav.__frameHover = false;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            if (event &amp;amp;&amp;amp; (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) return;&lt;br /&gt;
            if (toggleBodyScreen(portal) &amp;amp;&amp;amp; event) event.preventDefault();&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        hitLayer.appendChild(anchor);&lt;br /&gt;
        inner.appendChild(canvas);&lt;br /&gt;
        inner.appendChild(hitLayer);&lt;br /&gt;
        nav.appendChild(inner);&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        frame.applyCounterScale(&lt;br /&gt;
            [mount, nav],&lt;br /&gt;
            inner,&lt;br /&gt;
            frame.DIMENSIONS.ROW_HEIGHT&lt;br /&gt;
        );&lt;br /&gt;
        redraw();&lt;br /&gt;
&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-rendered&#039;, &#039;canvas&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-width&#039;, String(width));&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-label&#039;, label);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-scale&#039;, String(frame.getShellScale()));&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findPortal(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, attempt) {&lt;br /&gt;
        window.clearTimeout(retryTimer);&lt;br /&gt;
        retryTimer = window.setTimeout(function () {&lt;br /&gt;
            var portal = findPortal(root);&lt;br /&gt;
            if (!portal) return;&lt;br /&gt;
            if (!render(portal) &amp;amp;&amp;amp; attempt &amp;lt; 20) {&lt;br /&gt;
                schedule(root, attempt + 1);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (observer) observer.disconnect();&lt;br /&gt;
            if (window.ResizeObserver) {&lt;br /&gt;
                observer = new ResizeObserver(function () {&lt;br /&gt;
                    window.clearTimeout(resizeTimer);&lt;br /&gt;
                    resizeTimer = window.setTimeout(function () { render(portal); }, 60);&lt;br /&gt;
                });&lt;br /&gt;
                observer.observe(portal);&lt;br /&gt;
                var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
                var guideMount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
                if (topMount) observer.observe(topMount);&lt;br /&gt;
                if (guideMount) observer.observe(guideMount);&lt;br /&gt;
            }&lt;br /&gt;
        }, attempt ? 60 : 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot(root) {&lt;br /&gt;
        var scope = root || document;&lt;br /&gt;
        schedule(scope, 0);&lt;br /&gt;
        loadManifestoIntro(scope);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function bindFrameListeners() {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        if (!frame || frameListenersBound) return false;&lt;br /&gt;
        frameListenersBound = true;&lt;br /&gt;
        frame.onSeams(function () { schedule(document, 0); });&lt;br /&gt;
        frame.onScale(function () { schedule(document, 0); });&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    revealManifesto(document);&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { boot(document); }, { once:true });&lt;br /&gt;
    } else {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }&lt;br /&gt;
    window.addEventListener(&#039;load&#039;, function () { boot(document); }, { once:true });&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, function () { boot(document); });&lt;br /&gt;
    bindFrameListeners();&lt;br /&gt;
    document.addEventListener(&#039;framegeometryready&#039;, function () {&lt;br /&gt;
        if (bindFrameListeners()) schedule(document, 0);&lt;br /&gt;
    });&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            boot($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BottomGuideNav = {&lt;br /&gt;
        version:&#039;20260717-canvas-pixel-002&#039;,&lt;br /&gt;
        render:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? render(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        activate:function (screenName) {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? activateBodyScreen(portal, screenName) : false;&lt;br /&gt;
        },&lt;br /&gt;
        toggle:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? toggleBodyScreen(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        status:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            var mount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
            return {&lt;br /&gt;
                loaded:true,&lt;br /&gt;
                portal:!!portal,&lt;br /&gt;
                mount:!!mount,&lt;br /&gt;
                rendered:!!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-canvas&#039;)),&lt;br /&gt;
                generated:!!(mount &amp;amp;&amp;amp; mount.getAttribute(&#039;data-bottom-guide-generated&#039;)),&lt;br /&gt;
                panel:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;)),&lt;br /&gt;
                well:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-well&#039;)),&lt;br /&gt;
                insidePanel:!!(mount &amp;amp;&amp;amp; mount.parentNode &amp;amp;&amp;amp; mount.parentNode.classList.contains(&#039;main-body-panel&#039;)),&lt;br /&gt;
                welded:!!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-nav.is-continuous-weld&#039;)),&lt;br /&gt;
                activeScreen:portal ? portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039; : &#039;&#039;,&lt;br /&gt;
                guideScreen:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;)),&lt;br /&gt;
                width:mount ? mount.getAttribute(&#039;data-bottom-guide-width&#039;) || &#039;&#039; : &#039;&#039;&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4682</id>
		<title>미디어위키:CategoryNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4682"/>
		<updated>2026-07-17T08:18:58Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-canvas-pixel-20260717 / js/CategoryNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - CategoryNav&lt;br /&gt;
대문 카테고리 canvas 픽셀 네비 생성기&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var ITEMS = [&lt;br /&gt;
        { key:&#039;era&#039;, label:&#039;시대&#039;, title:&#039;시대&#039;, slant:&#039;right&#039; },&lt;br /&gt;
        { key:&#039;settings&#039;, label:&#039;설정&#039;, title:&#039;설정&#039;, slant:&#039;right&#039; },&lt;br /&gt;
        { key:&#039;project&#039;, label:&#039;프로젝트&#039;, title:&#039;프로젝트:소개&#039;, slant:&#039;both&#039;, pillar:true },&lt;br /&gt;
        { key:&#039;corporations&#039;, label:&#039;기업 및 공동체&#039;, title:&#039;기업_및_공동체&#039;, slant:&#039;left&#039; },&lt;br /&gt;
        { key:&#039;military&#039;, label:&#039;군, 정치집단&#039;, title:&#039;군_정치집단&#039;, slant:&#039;left&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
&lt;br /&gt;
    function api() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) return window.buildWikiPath(title);&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearNode(node) {&lt;br /&gt;
        while (node &amp;amp;&amp;amp; node.firstChild) node.removeChild(node.firstChild);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateLayout(width) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var f = frame.FRAME;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var contentX = d.WELL_TOP;&lt;br /&gt;
        var contentWidth = Math.max(ITEMS.length * 8, width - contentX * 2);&lt;br /&gt;
        var btnW = Math.max(4, Math.floor((contentWidth - f.GAP * 4) / ITEMS.length));&lt;br /&gt;
        var segments = [];&lt;br /&gt;
        var xoff = contentX;&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; ITEMS.length; i++) {&lt;br /&gt;
            segments.push({&lt;br /&gt;
                index:i,&lt;br /&gt;
                key:ITEMS[i].key,&lt;br /&gt;
                label:ITEMS[i].label,&lt;br /&gt;
                slant:ITEMS[i].slant,&lt;br /&gt;
                x:xoff,&lt;br /&gt;
                y:d.BUTTON_EDGE_TOP,&lt;br /&gt;
                btnW:btnW,&lt;br /&gt;
                pixelWidth:btnW + 2,&lt;br /&gt;
                pixelHeight:d.BUTTON_TOTAL&lt;br /&gt;
            });&lt;br /&gt;
            xoff += btnW + f.GAP;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            width:width,&lt;br /&gt;
            height:d.ROW_HEIGHT,&lt;br /&gt;
            contentX:contentX,&lt;br /&gt;
            contentWidth:contentWidth,&lt;br /&gt;
            buttonWidth:btnW,&lt;br /&gt;
            usedRight:xoff - f.GAP,&lt;br /&gt;
            segments:segments&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function paintProjectPillar(image, width, height, segment) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE);&lt;br /&gt;
        var leftTop = segment.x - topStep;&lt;br /&gt;
        var rightTop = segment.x + segment.btnW + topStep;&lt;br /&gt;
        var y;&lt;br /&gt;
        var x;&lt;br /&gt;
&lt;br /&gt;
        for (y = 0; y &amp;lt; height; y++) {&lt;br /&gt;
            var step = Math.floor(y / frame.FRAME.SLANT_RISE);&lt;br /&gt;
            var left = leftTop + step;&lt;br /&gt;
            var right = rightTop - step;&lt;br /&gt;
            for (x = left; x &amp;lt;= right; x++) frame.setPixel(image, width, height, x, y, &#039;S&#039;);&lt;br /&gt;
            frame.setPixel(image, width, height, left, y, &#039;D&#039;);&lt;br /&gt;
            frame.setPixel(image, width, height, right, y, &#039;H&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            leftTop:leftTop,&lt;br /&gt;
            rightTop:rightTop,&lt;br /&gt;
            leftAtButton:segment.x,&lt;br /&gt;
            rightAtButton:segment.x + segment.btnW&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function labelCenter(segment) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var ys = Math.floor(frame.FRAME.BUTTON_FACE / 2);&lt;br /&gt;
        var mask = frame.buttonMask(segment.btnW, ys, segment.slant, 0);&lt;br /&gt;
        return segment.x + Math.round((mask.left + mask.right) / 2);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function draw(canvas, layout, hoverIndex) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        var image = frame.createImage(ctx, layout.width, layout.height, &#039;BG&#039;);&lt;br /&gt;
        var projectGeometry = null;&lt;br /&gt;
&lt;br /&gt;
        frame.paintStandardFrame(image, layout.width, layout.height);&lt;br /&gt;
        layout.segments.forEach(function (segment) {&lt;br /&gt;
            var grid = frame.buildButtonPixels(&lt;br /&gt;
                segment.btnW,&lt;br /&gt;
                frame.FRAME.BUTTON_FACE,&lt;br /&gt;
                segment.slant,&lt;br /&gt;
                0&lt;br /&gt;
            );&lt;br /&gt;
            frame.paintGrid(&lt;br /&gt;
                image,&lt;br /&gt;
                layout.width,&lt;br /&gt;
                layout.height,&lt;br /&gt;
                grid,&lt;br /&gt;
                segment.x,&lt;br /&gt;
                segment.y,&lt;br /&gt;
                true&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        projectGeometry = paintProjectPillar(&lt;br /&gt;
            image,&lt;br /&gt;
            layout.width,&lt;br /&gt;
            layout.height,&lt;br /&gt;
            layout.segments[2]&lt;br /&gt;
        );&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
&lt;br /&gt;
        layout.segments.forEach(function (segment, index) {&lt;br /&gt;
            var center = index === 2&lt;br /&gt;
                ? Math.round((projectGeometry.leftAtButton + projectGeometry.rightAtButton) / 2)&lt;br /&gt;
                : labelCenter(segment);&lt;br /&gt;
            frame.drawLabel(&lt;br /&gt;
                ctx,&lt;br /&gt;
                segment.label,&lt;br /&gt;
                center,&lt;br /&gt;
                Math.round((frame.DIMENSIONS.BUTTON_SURFACE_TOP + frame.DIMENSIONS.BUTTON_SURFACE_BOTTOM) / 2) + 1,&lt;br /&gt;
                hoverIndex === index &amp;amp;&amp;amp; index !== 2,&lt;br /&gt;
                layout.width &amp;lt;= 820 ? 11 : 12&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
        canvas.__projectGeometry = projectGeometry;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function publishSeams(layout) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var settings = layout.segments[1];&lt;br /&gt;
        var project = layout.segments[2];&lt;br /&gt;
        var corporations = layout.segments[3];&lt;br /&gt;
        var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE);&lt;br /&gt;
        var settingsLeftOriginX = settings.x - topStep;&lt;br /&gt;
        var corpRightOriginX = corporations.x + corporations.btnW + topStep;&lt;br /&gt;
        var projectLeftOriginX = project.x - topStep;&lt;br /&gt;
        var projectRightOriginX = project.x + project.btnW + topStep;&lt;br /&gt;
        var seamStep = Math.floor(d.BOTTOM_START_ABS_Y / frame.FRAME.SLANT_RISE);&lt;br /&gt;
&lt;br /&gt;
        frame.setSeams({&lt;br /&gt;
            width:layout.width,&lt;br /&gt;
            rowHeight:d.ROW_HEIGHT,&lt;br /&gt;
            buttonTop:d.BUTTON_EDGE_TOP,&lt;br /&gt;
            buttonBottom:d.BUTTON_EDGE_BOTTOM,&lt;br /&gt;
            overlap:d.OVERLAP,&lt;br /&gt;
            bottomStartAbsY:d.BOTTOM_START_ABS_Y,&lt;br /&gt;
            phase:d.BOTTOM_START_ABS_Y % frame.FRAME.SLANT_RISE,&lt;br /&gt;
            settingsLeftOriginX:settingsLeftOriginX,&lt;br /&gt;
            settingsLeftTopX:settings.x,&lt;br /&gt;
            settingsLeftSeamX:settingsLeftOriginX + seamStep,&lt;br /&gt;
            settingsLeftDir:1,&lt;br /&gt;
            corpRightOriginX:corpRightOriginX,&lt;br /&gt;
            corpRightTopX:corporations.x + corporations.btnW,&lt;br /&gt;
            corpRightSeamX:corpRightOriginX - seamStep,&lt;br /&gt;
            corpRightDir:-1,&lt;br /&gt;
            projectLeftOriginX:projectLeftOriginX,&lt;br /&gt;
            projectRightOriginX:projectRightOriginX,&lt;br /&gt;
            projectLeftTopX:project.x,&lt;br /&gt;
            projectRightTopX:project.x + project.btnW,&lt;br /&gt;
            projectLeftDir:1,&lt;br /&gt;
            projectRightDir:-1,&lt;br /&gt;
            buttonWidth:layout.buttonWidth,&lt;br /&gt;
            segments:layout.segments.map(function (segment) {&lt;br /&gt;
                return {&lt;br /&gt;
                    key:segment.key,&lt;br /&gt;
                    x:segment.x,&lt;br /&gt;
                    y:segment.y,&lt;br /&gt;
                    btnW:segment.btnW,&lt;br /&gt;
                    pixelWidth:segment.pixelWidth,&lt;br /&gt;
                    slant:segment.slant&lt;br /&gt;
                };&lt;br /&gt;
            }),&lt;br /&gt;
            shellScale:frame.getShellScale()&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createHit(item, index, segment, nav, redraw) {&lt;br /&gt;
        var hit;&lt;br /&gt;
        if (item.pillar) {&lt;br /&gt;
            hit = document.createElement(&#039;span&#039;);&lt;br /&gt;
            hit.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
            hit.setAttribute(&#039;role&#039;, &#039;presentation&#039;);&lt;br /&gt;
            hit.className = &#039;portal-frame-hit portal-category-pillar-hit&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            hit = document.createElement(&#039;a&#039;);&lt;br /&gt;
            hit.href = buildPath(item.title);&lt;br /&gt;
            hit.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
            hit.className = &#039;portal-frame-hit portal-cat-anchor&#039;;&lt;br /&gt;
            hit.setAttribute(&#039;data-category-key&#039;, item.key);&lt;br /&gt;
            hit.addEventListener(&#039;mouseenter&#039;, function () {&lt;br /&gt;
                nav.__frameHoverIndex = index;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
            hit.addEventListener(&#039;mouseleave&#039;, function () {&lt;br /&gt;
                if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
            hit.addEventListener(&#039;focus&#039;, function () {&lt;br /&gt;
                nav.__frameHoverIndex = index;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
            hit.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
                if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;&lt;br /&gt;
                redraw();&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        hit.style.left = segment.x + &#039;px&#039;;&lt;br /&gt;
        hit.style.top = segment.y + &#039;px&#039;;&lt;br /&gt;
        hit.style.width = segment.pixelWidth + &#039;px&#039;;&lt;br /&gt;
        hit.style.height = segment.pixelHeight + &#039;px&#039;;&lt;br /&gt;
        hit.appendChild(document.createElement(&#039;span&#039;));&lt;br /&gt;
        hit.lastChild.className = &#039;portal-frame-sr-label&#039;;&lt;br /&gt;
        hit.lastChild.textContent = item.label;&lt;br /&gt;
        return hit;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderMount(mount) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var width;&lt;br /&gt;
        var layout;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var inner;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var hitLayer;&lt;br /&gt;
        var redraw;&lt;br /&gt;
&lt;br /&gt;
        if (!mount || !frame) return false;&lt;br /&gt;
        frame.setShellScale(frame.getShellScale());&lt;br /&gt;
        width = frame.readRenderWidth(mount);&lt;br /&gt;
        if (!width) {&lt;br /&gt;
            if (!mount.__categoryNavRetry) {&lt;br /&gt;
                mount.__categoryNavRetry = window.setTimeout(function () {&lt;br /&gt;
                    mount.__categoryNavRetry = 0;&lt;br /&gt;
                    renderMount(mount);&lt;br /&gt;
                }, 60);&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        layout = calculateLayout(width);&lt;br /&gt;
        if (&lt;br /&gt;
            mount.getAttribute(&#039;data-category-nav-width&#039;) === String(width) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-category-nav-scale&#039;) === String(frame.getShellScale()) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.querySelector(&#039;.portal-category-canvas&#039;)&lt;br /&gt;
        ) {&lt;br /&gt;
            publishSeams(layout);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-frame-canvas-nav&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, &#039;Main categories&#039;);&lt;br /&gt;
        nav.__frameHoverIndex = -1;&lt;br /&gt;
&lt;br /&gt;
        inner = document.createElement(&#039;div&#039;);&lt;br /&gt;
        inner.className = &#039;portal-frame-counter-inner portal-category-counter-inner&#039;;&lt;br /&gt;
        inner.style.width = width + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        canvas = frame.createCanvas(&#039;portal-frame-canvas portal-category-canvas&#039;, width, layout.height);&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-width&#039;, String(width));&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-height&#039;, String(layout.height));&lt;br /&gt;
&lt;br /&gt;
        hitLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        hitLayer.className = &#039;portal-frame-hit-layer&#039;;&lt;br /&gt;
        hitLayer.style.width = width + &#039;px&#039;;&lt;br /&gt;
        hitLayer.style.height = layout.height + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        redraw = function () {&lt;br /&gt;
            draw(canvas, layout, nav.__frameHoverIndex);&lt;br /&gt;
        };&lt;br /&gt;
        ITEMS.forEach(function (item, index) {&lt;br /&gt;
            hitLayer.appendChild(createHit(item, index, layout.segments[index], nav, redraw));&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        inner.appendChild(canvas);&lt;br /&gt;
        inner.appendChild(hitLayer);&lt;br /&gt;
        nav.appendChild(inner);&lt;br /&gt;
        clearNode(mount);&lt;br /&gt;
        mount.appendChild(nav);&lt;br /&gt;
        frame.applyCounterScale([mount, nav], inner, layout.height);&lt;br /&gt;
        redraw();&lt;br /&gt;
        publishSeams(layout);&lt;br /&gt;
&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-rendered&#039;, &#039;canvas&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-width&#039;, String(width));&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-scale&#039;, String(frame.getShellScale()));&lt;br /&gt;
&lt;br /&gt;
        if (window.ResizeObserver &amp;amp;&amp;amp; !mount.__categoryNavObserver) {&lt;br /&gt;
            mount.__categoryNavObserver = new window.ResizeObserver(function () {&lt;br /&gt;
                schedule(mount);&lt;br /&gt;
            });&lt;br /&gt;
            mount.__categoryNavObserver.observe(mount);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var mounts = [];&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;)) mounts.push(scope);&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;), function (mount) {&lt;br /&gt;
            if (mounts.indexOf(mount) === -1) mounts.push(mount);&lt;br /&gt;
        });&lt;br /&gt;
        mounts.forEach(function (mount) {&lt;br /&gt;
            if (renderMount(mount)) rendered += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { renderAll(document); });&lt;br /&gt;
    } else {&lt;br /&gt;
        renderAll(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (api()) api().onScale(function () { schedule(document); });&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            schedule(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () { schedule(document); });&lt;br /&gt;
&lt;br /&gt;
    window.CategoryNav = {&lt;br /&gt;
        version:&#039;20260717-canvas-pixel-001&#039;,&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderAll:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
    window.CLBI = window.CLBI || {};&lt;br /&gt;
    window.CLBI.categoryNav = {&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4681</id>
		<title>미디어위키:BottomGuideNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4681"/>
		<updated>2026-07-17T08:18:57Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-canvas-pixel-20260717 / js/BottomGuideNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
BottomGuideNav&lt;br /&gt;
대문 하단 「길라잡이」 canvas 픽셀 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var COMPONENT = &#039;category-guide-nav&#039;;&lt;br /&gt;
    var retryTimer = 0;&lt;br /&gt;
    var geometryWaitTimer = 0;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var observer = null;&lt;br /&gt;
    var manifestoIntroPromise = null;&lt;br /&gt;
    var MANIFESTO_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var MANIFESTO_INTRO_VERSION = &#039;20260713-divider-equal-gap-005&#039;;&lt;br /&gt;
    var GUIDE_DUMMY_ITEMS = [&lt;br /&gt;
        { key:&#039;start&#039;, label:&#039;처음 오셨다면&#039; },&lt;br /&gt;
        { key:&#039;world&#039;, label:&#039;세계관&#039; },&lt;br /&gt;
        { key:&#039;era&#039;, label:&#039;시대&#039; },&lt;br /&gt;
        { key:&#039;people&#039;, label:&#039;인물&#039; },&lt;br /&gt;
        { key:&#039;anecdote&#039;, label:&#039;에넥도트&#039; },&lt;br /&gt;
        { key:&#039;participate&#039;, label:&#039;참여 안내&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function api() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function revealManifesto(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        var portal = scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;) || document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var copy;&lt;br /&gt;
&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-pending&#039;);&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-active&#039;);&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-prepared&#039;);&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-running&#039;);&lt;br /&gt;
        title = portal.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        copy = portal.querySelector(&#039;.main-manifesto-copy&#039;);&lt;br /&gt;
        if (title) {&lt;br /&gt;
            title.style.opacity = &#039;1&#039;;&lt;br /&gt;
            title.style.borderBottom = &#039;0&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (copy) copy.style.visibility = &#039;visible&#039;;&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line&#039;), function (line) {&lt;br /&gt;
            line.style.height = &#039;26px&#039;;&lt;br /&gt;
            line.style.maxHeight = &#039;26px&#039;;&lt;br /&gt;
            line.style.opacity = &#039;1&#039;;&lt;br /&gt;
            line.style.overflow = &#039;visible&#039;;&lt;br /&gt;
            line.style.clipPath = &#039;inset(0 0 0 0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line-inner&#039;), function (inner) {&lt;br /&gt;
            inner.style.opacity = &#039;1&#039;;&lt;br /&gt;
            inner.style.transform = &#039;translateY(0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifestoIntro(root) {&lt;br /&gt;
        if (&lt;br /&gt;
            window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
            window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
            typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(window.ManifestoIntro.init(root || document)).catch(function () {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    return false;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                revealManifesto(root);&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (manifestoIntroPromise) return manifestoIntroPromise;&lt;br /&gt;
&lt;br /&gt;
        manifestoIntroPromise = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                if (!ok) {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    manifestoIntroPromise = null;&lt;br /&gt;
                }&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            script.src = &#039;/index.php?title=&#039; + encodeURIComponent(&#039;MediaWiki:ManifestoIntro.js&#039;) +&lt;br /&gt;
                &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&amp;amp;v=&#039; + encodeURIComponent(MANIFESTO_INTRO_VERSION) +&lt;br /&gt;
                &#039;&amp;amp;_manifestoFresh=&#039; + encodeURIComponent(String(Date.now()));&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-manifesto-intro-script&#039;, &#039;1&#039;);&lt;br /&gt;
            script.onload = function () {&lt;br /&gt;
                if (&lt;br /&gt;
                    window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
                    window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
                    typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    Promise.resolve(window.ManifestoIntro.init(root || document))&lt;br /&gt;
                        .then(function () { settle(true); })&lt;br /&gt;
                        .catch(function () { settle(false); });&lt;br /&gt;
                } else {&lt;br /&gt;
                    settle(false);&lt;br /&gt;
                }&lt;br /&gt;
            };&lt;br /&gt;
            script.onerror = function () { settle(false); };&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, MANIFESTO_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
        return manifestoIntroPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyPanel(portal) {&lt;br /&gt;
        var panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        var manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                else topMount.parentNode.appendChild(panel);&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        return { panel:panel, well:well };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBodyScreenHidden(screen, hidden) {&lt;br /&gt;
        if (!screen) return;&lt;br /&gt;
        screen.hidden = !!hidden;&lt;br /&gt;
        screen.setAttribute(&#039;aria-hidden&#039;, hidden ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        screen.classList.toggle(&#039;is-main-body-screen-hidden&#039;, !!hidden);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateGuideButtonState(portal, active) {&lt;br /&gt;
        var guideActive = active === &#039;guide&#039;;&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var target = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-anchor&#039;), function (anchor) {&lt;br /&gt;
            anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            anchor.setAttribute(&#039;data-main-body-target&#039;, target);&lt;br /&gt;
            anchor.removeAttribute(&#039;aria-current&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-label&#039;), function (text) {&lt;br /&gt;
            text.textContent = label;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-nav&#039;), function (nav) {&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureGuideDummyMenu(guide) {&lt;br /&gt;
        var menu;&lt;br /&gt;
        if (!guide) return null;&lt;br /&gt;
        menu = guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
        if (menu) return menu;&lt;br /&gt;
&lt;br /&gt;
        menu = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        menu.className = &#039;main-guide-menu&#039;;&lt;br /&gt;
        menu.setAttribute(&#039;data-guide-dummy-menu&#039;, &#039;1&#039;);&lt;br /&gt;
        menu.setAttribute(&#039;aria-label&#039;, &#039;길라잡이 항목 예시&#039;);&lt;br /&gt;
        GUIDE_DUMMY_ITEMS.forEach(function (item, index) {&lt;br /&gt;
            var wrapper = document.createElement(&#039;div&#039;);&lt;br /&gt;
            var button = document.createElement(&#039;button&#039;);&lt;br /&gt;
            var number = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var arrow = document.createElement(&#039;span&#039;);&lt;br /&gt;
            wrapper.className = &#039;main-guide-menu-item&#039;;&lt;br /&gt;
            wrapper.style.setProperty(&#039;--guide-menu-order&#039;, String(index));&lt;br /&gt;
            button.type = &#039;button&#039;;&lt;br /&gt;
            button.className = &#039;main-guide-menu-button&#039;;&lt;br /&gt;
            button.setAttribute(&#039;data-guide-dummy-button&#039;, item.key);&lt;br /&gt;
            button.setAttribute(&#039;aria-label&#039;, item.label + &#039; (더미 버튼)&#039;);&lt;br /&gt;
            number.className = &#039;main-guide-menu-number&#039;;&lt;br /&gt;
            number.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            number.textContent = String(index + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
            label.className = &#039;main-guide-menu-label&#039;;&lt;br /&gt;
            label.textContent = item.label;&lt;br /&gt;
            arrow.className = &#039;main-guide-menu-arrow&#039;;&lt;br /&gt;
            arrow.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            arrow.textContent = &#039;›&#039;;&lt;br /&gt;
            button.appendChild(number);&lt;br /&gt;
            button.appendChild(label);&lt;br /&gt;
            button.appendChild(arrow);&lt;br /&gt;
            button.addEventListener(&#039;click&#039;, function (event) { event.preventDefault(); });&lt;br /&gt;
            wrapper.appendChild(button);&lt;br /&gt;
            menu.appendChild(wrapper);&lt;br /&gt;
        });&lt;br /&gt;
        guide.insertBefore(menu, guide.firstChild);&lt;br /&gt;
        return menu;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function restartGuideMenuAnimation(guide) {&lt;br /&gt;
        if (!guide || !guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;)) return;&lt;br /&gt;
        guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        void guide.offsetWidth;&lt;br /&gt;
        guide.classList.add(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyScreens(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var manifesto = layout.well.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
        var guide = layout.well.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;);&lt;br /&gt;
        var panels;&lt;br /&gt;
        var active = layout.well.getAttribute(&#039;data-active-main-body-screen&#039;) ||&lt;br /&gt;
            portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (manifesto) {&lt;br /&gt;
            manifesto.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            manifesto.setAttribute(&#039;data-main-body-screen&#039;, &#039;manifesto&#039;);&lt;br /&gt;
            if (!manifesto.id) manifesto.id = &#039;main-manifesto-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (!guide) {&lt;br /&gt;
            guide = document.createElement(&#039;section&#039;);&lt;br /&gt;
            guide.className = &#039;main-body-screen main-guide-screen&#039;;&lt;br /&gt;
            guide.setAttribute(&#039;data-main-body-screen&#039;, &#039;guide&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;data-main-guide-screen-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;aria-label&#039;, &#039;길라잡이&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;role&#039;, &#039;region&#039;);&lt;br /&gt;
            guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
            layout.well.appendChild(guide);&lt;br /&gt;
        } else {&lt;br /&gt;
            guide.classList.add(&#039;main-body-screen&#039;, &#039;main-guide-screen&#039;);&lt;br /&gt;
            if (!guide.id) guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        ensureGuideDummyMenu(guide);&lt;br /&gt;
        panels = Array.prototype.slice.call(&lt;br /&gt;
            layout.well.querySelectorAll(&#039;.main-body-screen[data-main-body-screen]&#039;)&lt;br /&gt;
        );&lt;br /&gt;
        if (active !== &#039;guide&#039;) active = &#039;manifesto&#039;;&lt;br /&gt;
        if (!panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === active;&lt;br /&gt;
        })) active = &#039;manifesto&#039;;&lt;br /&gt;
        panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(panel, panel.getAttribute(&#039;data-main-body-screen&#039;) !== active);&lt;br /&gt;
        });&lt;br /&gt;
        layout.well.setAttribute(&#039;data-main-body-screen-controller-ready&#039;, &#039;1&#039;);&lt;br /&gt;
        layout.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; !guide.classList.contains(&#039;is-guide-menu-entering&#039;)) {&lt;br /&gt;
            restartGuideMenuAnimation(guide);&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            well:layout.well,&lt;br /&gt;
            panels:panels,&lt;br /&gt;
            manifesto:manifesto,&lt;br /&gt;
            guide:guide,&lt;br /&gt;
            active:active&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBodyScreen(portal, screenName) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var previous;&lt;br /&gt;
        var active;&lt;br /&gt;
        var event;&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        screenName = screenName === &#039;guide&#039; ? &#039;guide&#039; : &#039;manifesto&#039;;&lt;br /&gt;
        if (!screens.panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === screenName;&lt;br /&gt;
        })) return false;&lt;br /&gt;
        previous = screens.active;&lt;br /&gt;
        active = screenName;&lt;br /&gt;
        screens.panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(panel, panel.getAttribute(&#039;data-main-body-screen&#039;) !== active);&lt;br /&gt;
        });&lt;br /&gt;
        screens.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; previous !== active) restartGuideMenuAnimation(screens.guide);&lt;br /&gt;
        else if (active !== &#039;guide&#039; &amp;amp;&amp;amp; screens.guide) screens.guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        render(portal);&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            try {&lt;br /&gt;
                event = new CustomEvent(&#039;mainbodyscreenchange&#039;, {&lt;br /&gt;
                    bubbles:true,&lt;br /&gt;
                    detail:{ screen:active, previous:previous }&lt;br /&gt;
                });&lt;br /&gt;
                portal.dispatchEvent(event);&lt;br /&gt;
            } catch (error) {}&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function toggleBodyScreen(portal) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        return activateBodyScreen(portal, screens.active === &#039;guide&#039; ? &#039;manifesto&#039; : &#039;guide&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleGuideScreenCapture(event) {&lt;br /&gt;
        var target;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var portal;&lt;br /&gt;
        if (!event || event.__mainGuideScreenHandled) return;&lt;br /&gt;
        if (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;&lt;br /&gt;
        target = event.target;&lt;br /&gt;
        anchor = target &amp;amp;&amp;amp; target.closest&lt;br /&gt;
            ? target.closest(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)&lt;br /&gt;
            : null;&lt;br /&gt;
        if (!anchor) return;&lt;br /&gt;
        portal = anchor.closest ? anchor.closest(&#039;.main-portal&#039;) : null;&lt;br /&gt;
        if (!portal || !toggleBodyScreen(portal)) return;&lt;br /&gt;
        event.__mainGuideScreenHandled = true;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) event.stopImmediatePropagation();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (window.BottomGuideNavScreenCaptureHandler) {&lt;br /&gt;
        document.removeEventListener(&#039;click&#039;, window.BottomGuideNavScreenCaptureHandler, true);&lt;br /&gt;
    }&lt;br /&gt;
    window.BottomGuideNavScreenCaptureHandler = handleGuideScreenCapture;&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleGuideScreenCapture, true);&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
        ensureBodyScreens(portal);&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
            mount.setAttribute(&#039;data-bottom-guide-generated&#039;, &#039;1&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        if (mount.parentNode !== layout.panel) layout.panel.appendChild(mount);&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function guideButton(seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var step = Math.floor(d.BUTTON_EDGE_BOTTOM / frame.FRAME.SLANT_RISE);&lt;br /&gt;
        var left = seams.settingsLeftOriginX + step;&lt;br /&gt;
        var right = seams.corpRightOriginX - step;&lt;br /&gt;
        return {&lt;br /&gt;
            x:Math.round(left),&lt;br /&gt;
            y:d.BUTTON_EDGE_TOP,&lt;br /&gt;
            btnW:Math.max(4, Math.round(right - left)),&lt;br /&gt;
            pixelHeight:d.BUTTON_TOTAL,&lt;br /&gt;
            slant:&#039;both&#039;&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function draw(canvas, button, label, hover) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        var image = frame.createImage(ctx, canvas.width, canvas.height, &#039;BG&#039;);&lt;br /&gt;
        var grid;&lt;br /&gt;
        var mask;&lt;br /&gt;
        var center;&lt;br /&gt;
&lt;br /&gt;
        frame.paintStandardFrame(image, canvas.width, canvas.height);&lt;br /&gt;
        grid = frame.buildReverseButtonPixels(&lt;br /&gt;
            button.btnW,&lt;br /&gt;
            frame.FRAME.BUTTON_FACE,&lt;br /&gt;
            button.slant,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        frame.paintGrid(image, canvas.width, canvas.height, grid, button.x, button.y, true);&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
        mask = frame.buttonMask(&lt;br /&gt;
            button.btnW,&lt;br /&gt;
            frame.FRAME.BUTTON_FACE - 1 - Math.floor(frame.FRAME.BUTTON_FACE / 2),&lt;br /&gt;
            button.slant,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        center = button.x + Math.round((mask.left + mask.right) / 2);&lt;br /&gt;
        frame.drawLabel(&lt;br /&gt;
            ctx,&lt;br /&gt;
            label,&lt;br /&gt;
            center,&lt;br /&gt;
            Math.round((frame.DIMENSIONS.BUTTON_SURFACE_TOP + frame.DIMENSIONS.BUTTON_SURFACE_BOTTOM) / 2) + 1,&lt;br /&gt;
            hover,&lt;br /&gt;
            canvas.width &amp;lt;= 820 ? 11 : 12&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(portal) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var seams;&lt;br /&gt;
        var mount;&lt;br /&gt;
        var width;&lt;br /&gt;
        var guideActive;&lt;br /&gt;
        var label;&lt;br /&gt;
        var targetScreen;&lt;br /&gt;
        var href;&lt;br /&gt;
        var button;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var inner;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var hitLayer;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var hiddenLabel;&lt;br /&gt;
        var redraw;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !frame.FRAME || typeof frame.readRenderWidth !== &#039;function&#039;) {&lt;br /&gt;
            window.clearTimeout(geometryWaitTimer);&lt;br /&gt;
            geometryWaitTimer = window.setTimeout(function () {&lt;br /&gt;
                geometryWaitTimer = 0;&lt;br /&gt;
                render(portal);&lt;br /&gt;
            }, 50);&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        seams = frame.seams;&lt;br /&gt;
        if (!seams || !seams.width) return false;&lt;br /&gt;
        mount = ensureMount(portal);&lt;br /&gt;
        width = seams.width || frame.readRenderWidth(mount);&lt;br /&gt;
        if (!width) return false;&lt;br /&gt;
&lt;br /&gt;
        guideActive = portal.getAttribute(&#039;data-main-body-screen&#039;) === &#039;guide&#039;;&lt;br /&gt;
        label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        targetScreen = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
        button = guideButton(seams);&lt;br /&gt;
        if (&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-width&#039;) === String(width) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-label&#039;) === label &amp;amp;&amp;amp;&lt;br /&gt;
            mount.getAttribute(&#039;data-bottom-guide-scale&#039;) === String(frame.getShellScale()) &amp;amp;&amp;amp;&lt;br /&gt;
            mount.querySelector(&#039;.portal-guide-canvas&#039;)&lt;br /&gt;
        ) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-guide-nav portal-frame-canvas-nav is-continuous-weld&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        nav.__frameHover = false;&lt;br /&gt;
&lt;br /&gt;
        inner = document.createElement(&#039;div&#039;);&lt;br /&gt;
        inner.className = &#039;portal-frame-counter-inner portal-guide-counter-inner&#039;;&lt;br /&gt;
        inner.style.width = width + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        canvas = frame.createCanvas(&#039;portal-frame-canvas portal-guide-canvas&#039;, width, frame.DIMENSIONS.ROW_HEIGHT);&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-width&#039;, String(width));&lt;br /&gt;
        canvas.setAttribute(&#039;data-frame-height&#039;, String(frame.DIMENSIONS.ROW_HEIGHT));&lt;br /&gt;
&lt;br /&gt;
        hitLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        hitLayer.className = &#039;portal-frame-hit-layer&#039;;&lt;br /&gt;
        hitLayer.style.width = width + &#039;px&#039;;&lt;br /&gt;
        hitLayer.style.height = frame.DIMENSIONS.ROW_HEIGHT + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        anchor = document.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className = &#039;portal-frame-hit portal-guide-anchor&#039;;&lt;br /&gt;
        anchor.href = href;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        anchor.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, &#039;guide&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-main-body-target&#039;, targetScreen);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-controls&#039;, &#039;main-manifesto-screen main-guide-screen&#039;);&lt;br /&gt;
        anchor.style.left = button.x + &#039;px&#039;;&lt;br /&gt;
        anchor.style.top = button.y + &#039;px&#039;;&lt;br /&gt;
        anchor.style.width = (button.btnW + 2) + &#039;px&#039;;&lt;br /&gt;
        anchor.style.height = button.pixelHeight + &#039;px&#039;;&lt;br /&gt;
        hiddenLabel = document.createElement(&#039;span&#039;);&lt;br /&gt;
        hiddenLabel.className = &#039;portal-frame-sr-label portal-guide-label&#039;;&lt;br /&gt;
        hiddenLabel.textContent = label;&lt;br /&gt;
        anchor.appendChild(hiddenLabel);&lt;br /&gt;
&lt;br /&gt;
        redraw = function () { draw(canvas, button, label, nav.__frameHover); };&lt;br /&gt;
        anchor.addEventListener(&#039;mouseenter&#039;, function () {&lt;br /&gt;
            nav.__frameHover = true;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;mouseleave&#039;, function () {&lt;br /&gt;
            nav.__frameHover = false;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;focus&#039;, function () {&lt;br /&gt;
            nav.__frameHover = true;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
            nav.__frameHover = false;&lt;br /&gt;
            redraw();&lt;br /&gt;
        });&lt;br /&gt;
        anchor.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            if (event &amp;amp;&amp;amp; (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) return;&lt;br /&gt;
            if (toggleBodyScreen(portal) &amp;amp;&amp;amp; event) event.preventDefault();&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        hitLayer.appendChild(anchor);&lt;br /&gt;
        inner.appendChild(canvas);&lt;br /&gt;
        inner.appendChild(hitLayer);&lt;br /&gt;
        nav.appendChild(inner);&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        frame.applyCounterScale(&lt;br /&gt;
            [mount, nav],&lt;br /&gt;
            inner,&lt;br /&gt;
            frame.DIMENSIONS.ROW_HEIGHT&lt;br /&gt;
        );&lt;br /&gt;
        redraw();&lt;br /&gt;
&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-rendered&#039;, &#039;canvas&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-width&#039;, String(width));&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-label&#039;, label);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-scale&#039;, String(frame.getShellScale()));&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findPortal(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, attempt) {&lt;br /&gt;
        window.clearTimeout(retryTimer);&lt;br /&gt;
        retryTimer = window.setTimeout(function () {&lt;br /&gt;
            var portal = findPortal(root);&lt;br /&gt;
            if (!portal) return;&lt;br /&gt;
            if (!render(portal) &amp;amp;&amp;amp; attempt &amp;lt; 20) {&lt;br /&gt;
                schedule(root, attempt + 1);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (observer) observer.disconnect();&lt;br /&gt;
            if (window.ResizeObserver) {&lt;br /&gt;
                observer = new ResizeObserver(function () {&lt;br /&gt;
                    window.clearTimeout(resizeTimer);&lt;br /&gt;
                    resizeTimer = window.setTimeout(function () { render(portal); }, 60);&lt;br /&gt;
                });&lt;br /&gt;
                observer.observe(portal);&lt;br /&gt;
                var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
                var guideMount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
                if (topMount) observer.observe(topMount);&lt;br /&gt;
                if (guideMount) observer.observe(guideMount);&lt;br /&gt;
            }&lt;br /&gt;
        }, attempt ? 60 : 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot(root) {&lt;br /&gt;
        var scope = root || document;&lt;br /&gt;
        schedule(scope, 0);&lt;br /&gt;
        loadManifestoIntro(scope);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    revealManifesto(document);&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { boot(document); }, { once:true });&lt;br /&gt;
    } else {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }&lt;br /&gt;
    window.addEventListener(&#039;load&#039;, function () { boot(document); }, { once:true });&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, function () { boot(document); });&lt;br /&gt;
    if (api()) {&lt;br /&gt;
        api().onSeams(function () { schedule(document, 0); });&lt;br /&gt;
        api().onScale(function () { schedule(document, 0); });&lt;br /&gt;
    }&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            boot($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BottomGuideNav = {&lt;br /&gt;
        version:&#039;20260717-canvas-pixel-001&#039;,&lt;br /&gt;
        render:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? render(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        activate:function (screenName) {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? activateBodyScreen(portal, screenName) : false;&lt;br /&gt;
        },&lt;br /&gt;
        toggle:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? toggleBodyScreen(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        status:function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            var mount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
            return {&lt;br /&gt;
                loaded:true,&lt;br /&gt;
                portal:!!portal,&lt;br /&gt;
                mount:!!mount,&lt;br /&gt;
                rendered:!!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-canvas&#039;)),&lt;br /&gt;
                generated:!!(mount &amp;amp;&amp;amp; mount.getAttribute(&#039;data-bottom-guide-generated&#039;)),&lt;br /&gt;
                panel:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;)),&lt;br /&gt;
                well:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-well&#039;)),&lt;br /&gt;
                insidePanel:!!(mount &amp;amp;&amp;amp; mount.parentNode &amp;amp;&amp;amp; mount.parentNode.classList.contains(&#039;main-body-panel&#039;)),&lt;br /&gt;
                welded:!!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-nav.is-continuous-weld&#039;)),&lt;br /&gt;
                activeScreen:portal ? portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039; : &#039;&#039;,&lt;br /&gt;
                guideScreen:!!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;)),&lt;br /&gt;
                width:mount ? mount.getAttribute(&#039;data-bottom-guide-width&#039;) || &#039;&#039; : &#039;&#039;&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4680</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4680"/>
		<updated>2026-07-17T08:18:57Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-canvas-pixel-20260717 / css/MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:42px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
FrameGeometry.js가 공유한 설정 왼쪽 seam과 기업 및 공동체 오른쪽 seam을&lt;br /&gt;
같은 정수 좌표계에서 연장한다. 상단 프레임 하단 9px 안으로 겹쳐 접합하며,&lt;br /&gt;
표시 높이는 공통 ROW_HEIGHT 42px만 사용한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-outline {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:10%;&lt;br /&gt;
    right:auto;&lt;br /&gt;
    width:80%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 외곽 테두리를 포함한 42px 정수 단면을 사용한다. */&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단·하단 총 42px:&lt;br /&gt;
외곽1 + 프레임면7 + 우물2 + 버튼(테두리1 + 표면20 + 테두리1) + 우물2 + 프레임면7 + 외곽1&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
사선 프레임 단일 정수 좌표계 / 20260717&lt;br /&gt;
FrameGeometry.js의 1+7 / 2 / 1+20+1 / 2 / 7+1 정수 단면을 그대로 표시한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav,&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-highlight,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:focus .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame,&lt;br /&gt;
.main-portal .portal-section-frame,&lt;br /&gt;
.main-portal .portal-category-pillar,&lt;br /&gt;
.main-portal .portal-section-button-geometry,&lt;br /&gt;
.main-portal .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-hit {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 길라잡이도 같은 42px 정수 단면과 반대 사선을 사용한다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    stroke:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* main-body-panel의 1px 좌우 외곽선을 보정해 상단 seam 좌표폭과 일치시킨다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    left:-1px !important;&lt;br /&gt;
    right:-1px !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
}&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 사선 프레임 canvas 픽셀 렌더링 / 20260717&lt;br /&gt;
SVG polygon 표시 계층을 대체한다. shell scale은 안쪽 레이어에서 역보정한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-frame-counter-host {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    margin-right:0 !important;&lt;br /&gt;
    margin-left:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-canvas-nav {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-counter-inner {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    transform-origin:top left !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-canvas {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    image-rendering:pixelated;&lt;br /&gt;
    image-rendering:crisp-edges;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-hit-layer {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-hit {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    outline:0 !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-hit:focus-visible {&lt;br /&gt;
    outline:1px solid #e2e2e2 !important;&lt;br /&gt;
    outline-offset:-2px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-hit {&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    cursor:default !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-frame-sr-label {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    width:1px !important;&lt;br /&gt;
    height:1px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    margin:-1px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    clip:rect(0, 0, 0, 0) !important;&lt;br /&gt;
    white-space:nowrap !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-canvas,&lt;br /&gt;
.main-portal .portal-section-canvas,&lt;br /&gt;
.main-portal .portal-guide-canvas {&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after,&lt;br /&gt;
.main-portal .portal-category-svg,&lt;br /&gt;
.main-portal .portal-section-svg,&lt;br /&gt;
.main-portal .portal-guide-svg {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:PortalSectionNav.js&amp;diff=4679</id>
		<title>미디어위키:PortalSectionNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:PortalSectionNav.js&amp;diff=4679"/>
		<updated>2026-07-17T04:00:19Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-pixel-stack-20260717 / js/PortalSectionNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
PortalSectionNav&lt;br /&gt;
대문 상단 카테고리 하단 부착형 섹션 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var COMPONENT = &#039;portal-section-nav&#039;;&lt;br /&gt;
    var VERSION = &#039;20260717-pixel-stack-042&#039;;&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
    var ITEMS = [&lt;br /&gt;
        { key:&#039;black-ice&#039;, label:&#039;블랙 아이스&#039;, title:&#039;대문&#039; },&lt;br /&gt;
        { key:&#039;anecdote&#039;, label:&#039;에넥도트&#039;, href:&#039;/index.php/특수:모든문서?namespace=3000&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function api() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildWikiPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) return window.buildWikiPath(title);&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal, topMount) {&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
        }&lt;br /&gt;
        if (mount.parentNode !== topMount.parentNode || topMount.nextSibling !== mount) {&lt;br /&gt;
            topMount.parentNode.insertBefore(mount, topMount.nextSibling);&lt;br /&gt;
        }&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function outerGeometry(seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var phase = seams.bottomStartAbsY % frame.FRAME.SLANT_RISE;&lt;br /&gt;
        var leftSeamX = frame.slantX(&lt;br /&gt;
            seams.settingsLeftTopX,&lt;br /&gt;
            seams.bottomStartAbsY,&lt;br /&gt;
            seams.settingsLeftDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        var rightSeamX = frame.slantX(&lt;br /&gt;
            seams.corpRightTopX,&lt;br /&gt;
            seams.bottomStartAbsY,&lt;br /&gt;
            seams.corpRightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            phase:phase,&lt;br /&gt;
            leftSeamX:leftSeamX,&lt;br /&gt;
            rightSeamX:rightSeamX,&lt;br /&gt;
            leftDir:seams.settingsLeftDir,&lt;br /&gt;
            rightDir:seams.corpRightDir,&lt;br /&gt;
            points:frame.segment(&lt;br /&gt;
                leftSeamX,&lt;br /&gt;
                rightSeamX,&lt;br /&gt;
                d.ROW_HEIGHT,&lt;br /&gt;
                seams.settingsLeftDir,&lt;br /&gt;
                seams.corpRightDir,&lt;br /&gt;
                phase&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendFrame(svg, geometry) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var f = frame.FRAME;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var group = frame.create(&#039;g&#039;, &#039;portal-section-frame&#039;);&lt;br /&gt;
        var surface = frame.segmentAt(&lt;br /&gt;
            geometry.leftSeamX,&lt;br /&gt;
            geometry.rightSeamX,&lt;br /&gt;
            d.FRAME_SURFACE_TOP,&lt;br /&gt;
            d.ROW_HEIGHT - f.BORDER,&lt;br /&gt;
            geometry.leftDir,&lt;br /&gt;
            geometry.rightDir,&lt;br /&gt;
            geometry.phase,&lt;br /&gt;
            f.BORDER,&lt;br /&gt;
            f.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var well = frame.segmentAt(&lt;br /&gt;
            geometry.leftSeamX,&lt;br /&gt;
            geometry.rightSeamX,&lt;br /&gt;
            d.WELL_TOP,&lt;br /&gt;
            d.WELL2_BOTTOM,&lt;br /&gt;
            geometry.leftDir,&lt;br /&gt;
            geometry.rightDir,&lt;br /&gt;
            geometry.phase,&lt;br /&gt;
            d.WELL_TOP,&lt;br /&gt;
            d.WELL_TOP&lt;br /&gt;
        );&lt;br /&gt;
        var joinCover = frame.segmentAt(&lt;br /&gt;
            geometry.leftSeamX,&lt;br /&gt;
            geometry.rightSeamX,&lt;br /&gt;
            0,&lt;br /&gt;
            d.OVERLAP,&lt;br /&gt;
            geometry.leftDir,&lt;br /&gt;
            geometry.rightDir,&lt;br /&gt;
            geometry.phase,&lt;br /&gt;
            0,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        var frameTopRow = frame.rowAt(&lt;br /&gt;
            geometry.leftSeamX,&lt;br /&gt;
            geometry.rightSeamX,&lt;br /&gt;
            d.OVERLAP,&lt;br /&gt;
            geometry.leftDir,&lt;br /&gt;
            geometry.rightDir,&lt;br /&gt;
            geometry.phase,&lt;br /&gt;
            f.BORDER,&lt;br /&gt;
            f.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var frameBottomRow = frame.rowAt(&lt;br /&gt;
            geometry.leftSeamX,&lt;br /&gt;
            geometry.rightSeamX,&lt;br /&gt;
            d.ROW_HEIGHT - f.BORDER - f.BEVEL,&lt;br /&gt;
            geometry.leftDir,&lt;br /&gt;
            geometry.rightDir,&lt;br /&gt;
            geometry.phase,&lt;br /&gt;
            f.BORDER,&lt;br /&gt;
            f.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var wellTopRow = frame.rowAt(&lt;br /&gt;
            geometry.leftSeamX,&lt;br /&gt;
            geometry.rightSeamX,&lt;br /&gt;
            d.WELL_TOP,&lt;br /&gt;
            geometry.leftDir,&lt;br /&gt;
            geometry.rightDir,&lt;br /&gt;
            geometry.phase,&lt;br /&gt;
            d.WELL_TOP,&lt;br /&gt;
            d.WELL_TOP&lt;br /&gt;
        );&lt;br /&gt;
        var wellBottomRow = frame.rowAt(&lt;br /&gt;
            geometry.leftSeamX,&lt;br /&gt;
            geometry.rightSeamX,&lt;br /&gt;
            d.WELL2_BOTTOM - f.BEVEL,&lt;br /&gt;
            geometry.leftDir,&lt;br /&gt;
            geometry.rightDir,&lt;br /&gt;
            geometry.phase,&lt;br /&gt;
            d.WELL_TOP,&lt;br /&gt;
            d.WELL_TOP&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-section-frame-edge&#039;, geometry.points));&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-section-frame-surface&#039;, surface));&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-section-frame-well&#039;, well));&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-section-join-side-cover&#039;, joinCover));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-frame-shadow&#039;, frameBottomRow[0], frameTopRow[0]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-frame-highlight&#039;, frameTopRow[1], frameBottomRow[1]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-well-shadow&#039;, wellTopRow[0], wellTopRow[1]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-well-shadow&#039;, wellTopRow[1], wellBottomRow[1]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-well-highlight&#039;, wellBottomRow[0], wellTopRow[0]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-well-highlight&#039;, wellBottomRow[1], wellBottomRow[0]));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function splitButtons(geometry) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var f = frame.FRAME;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var leftBase = geometry.leftSeamX + d.WELL_BOTTOM;&lt;br /&gt;
        var rightBase = geometry.rightSeamX - d.WELL_BOTTOM;&lt;br /&gt;
        var leftTop = frame.slantX(leftBase, d.BUTTON_TOP, geometry.leftDir, geometry.phase);&lt;br /&gt;
        var rightTop = frame.slantX(rightBase, d.BUTTON_TOP, geometry.rightDir, geometry.phase);&lt;br /&gt;
        var topMid = Math.round((leftTop + rightTop) / 2);&lt;br /&gt;
        var halfGap = f.GAP / 2;&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                leftTopX:leftBase,&lt;br /&gt;
                rightTopX:topMid - halfGap,&lt;br /&gt;
                leftDir:geometry.leftDir,&lt;br /&gt;
                rightDir:0,&lt;br /&gt;
                phase:geometry.phase&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                leftTopX:topMid + halfGap,&lt;br /&gt;
                rightTopX:rightBase,&lt;br /&gt;
                leftDir:0,&lt;br /&gt;
                rightDir:geometry.rightDir,&lt;br /&gt;
                phase:geometry.phase&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendButton(svg, item, button) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var f = frame.FRAME;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var points = frame.segmentAt(&lt;br /&gt;
            button.leftTopX,&lt;br /&gt;
            button.rightTopX,&lt;br /&gt;
            d.BUTTON_EDGE_TOP,&lt;br /&gt;
            d.BUTTON_EDGE_BOTTOM,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            button.phase,&lt;br /&gt;
            0,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        var surface = frame.segmentAt(&lt;br /&gt;
            button.leftTopX,&lt;br /&gt;
            button.rightTopX,&lt;br /&gt;
            d.BUTTON_SURFACE_TOP,&lt;br /&gt;
            d.BUTTON_SURFACE_BOTTOM,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            button.phase,&lt;br /&gt;
            f.BORDER,&lt;br /&gt;
            f.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var topInset = frame.rowAt(&lt;br /&gt;
            button.leftTopX,&lt;br /&gt;
            button.rightTopX,&lt;br /&gt;
            d.BUTTON_SURFACE_TOP,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            button.phase,&lt;br /&gt;
            f.BORDER,&lt;br /&gt;
            f.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var bottomInset = frame.rowAt(&lt;br /&gt;
            button.leftTopX,&lt;br /&gt;
            button.rightTopX,&lt;br /&gt;
            d.BUTTON_SURFACE_BOTTOM - f.BEVEL,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            button.phase,&lt;br /&gt;
            f.BORDER,&lt;br /&gt;
            f.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var anchor = frame.create(&#039;a&#039;, &#039;portal-cat-anchor portal-section-anchor&#039;);&lt;br /&gt;
        var group = frame.create(&#039;g&#039;, &#039;portal-section-button-geometry&#039;);&lt;br /&gt;
        var hit = frame.polygon(&#039;portal-cat-hit&#039;, points);&lt;br /&gt;
        var text = frame.create(&#039;text&#039;, &#039;portal-cat-label portal-section-label&#039;);&lt;br /&gt;
        var href = item.href || buildWikiPath(item.title);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, item.key);&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-cat-edge&#039;, points));&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-cat-surface&#039;, surface));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-cat-inset portal-cat-inset-top&#039;, topInset[0], topInset[1]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-cat-inset portal-cat-inset-right&#039;, topInset[1], bottomInset[1]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-cat-inset portal-cat-inset-bottom&#039;, bottomInset[1], bottomInset[0]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-cat-inset portal-cat-inset-left&#039;, bottomInset[0], topInset[0]));&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
        group.appendChild(hit);&lt;br /&gt;
        anchor.appendChild(group);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, frame.centerX(surface));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, Math.round((d.BUTTON_SURFACE_TOP + d.BUTTON_SURFACE_BOTTOM) / 2) + 1);&lt;br /&gt;
        text.setAttribute(&#039;font-size&#039;, &#039;12&#039;);&lt;br /&gt;
        text.setAttribute(&#039;font-weight&#039;, &#039;700&#039;);&lt;br /&gt;
        text.setAttribute(&#039;text-anchor&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;dominant-baseline&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.textContent = item.label;&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        svg.appendChild(anchor);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPortal(portal, seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var current = seams || (frame &amp;amp;&amp;amp; frame.seams);&lt;br /&gt;
        var topMount;&lt;br /&gt;
        var mount;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var svg;&lt;br /&gt;
        var geometry;&lt;br /&gt;
        var buttons;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !portal || !current || !current.width) return false;&lt;br /&gt;
        topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        if (!topMount) return false;&lt;br /&gt;
        mount = ensureMount(portal, topMount);&lt;br /&gt;
        geometry = outerGeometry(current);&lt;br /&gt;
        buttons = splitButtons(geometry);&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        nav.className = &#039;portal-section-nav&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, &#039;작품 영역&#039;);&lt;br /&gt;
        svg = frame.create(&#039;svg&#039;, &#039;portal-section-svg&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + current.width + &#039; &#039; + frame.DIMENSIONS.ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, frame.DIMENSIONS.ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;none&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;data-frame-width&#039;, current.width);&lt;br /&gt;
&lt;br /&gt;
        appendFrame(svg, geometry);&lt;br /&gt;
        ITEMS.forEach(function (item, index) { appendButton(svg, item, buttons[index]); });&lt;br /&gt;
        nav.appendChild(svg);&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-width&#039;, String(current.width));&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-phase&#039;, String(geometry.phase));&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectPortals(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var portals = [];&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)) portals.push(scope);&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;.main-portal&#039;), function (portal) {&lt;br /&gt;
            if (portals.indexOf(portal) === -1) portals.push(portal);&lt;br /&gt;
        });&lt;br /&gt;
        return portals;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root, seams) {&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
        collectPortals(root).forEach(function (portal) {&lt;br /&gt;
            if (renderPortal(portal, seams)) rendered += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, seams) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document, seams);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (api()) api().onSeams(function (seams) { schedule(document, seams); });&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            schedule(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.PortalSectionNav = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        render:renderPortal,&lt;br /&gt;
        renderAll:renderAll&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:FrameGeometry.js&amp;diff=4678</id>
		<title>미디어위키:FrameGeometry.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:FrameGeometry.js&amp;diff=4678"/>
		<updated>2026-07-17T04:00:19Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-pixel-stack-20260717 / js/FrameGeometry.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
FrameGeometry&lt;br /&gt;
대문 사선 프레임 공통 정수 좌표계&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (w, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    var FRAME = {&lt;br /&gt;
        BORDER:1,&lt;br /&gt;
        BEVEL:1,&lt;br /&gt;
        SLANT_RISE:2,&lt;br /&gt;
        SLANT_RUN:1,&lt;br /&gt;
        BUTTON_FACE:20,&lt;br /&gt;
        FRAME_FACE:7,&lt;br /&gt;
        GAP:2,&lt;br /&gt;
        COLORS:{&lt;br /&gt;
            border:&#039;#050505&#039;,&lt;br /&gt;
            surface:&#039;#1d1d1d&#039;,&lt;br /&gt;
            well:&#039;#080808&#039;,&lt;br /&gt;
            highlight:&#039;#555555&#039;,&lt;br /&gt;
            shadow:&#039;#101010&#039;,&lt;br /&gt;
            label:&#039;#e2e2e2&#039;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    var DIMENSIONS = {&lt;br /&gt;
        FRAME_SURFACE_TOP:FRAME.BORDER,&lt;br /&gt;
        FRAME_SURFACE_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE,&lt;br /&gt;
        WELL_TOP:FRAME.BORDER + FRAME.FRAME_FACE,&lt;br /&gt;
        WELL_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP,&lt;br /&gt;
        BUTTON_EDGE_TOP:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP,&lt;br /&gt;
        BUTTON_SURFACE_TOP:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER,&lt;br /&gt;
        BUTTON_SURFACE_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE,&lt;br /&gt;
        BUTTON_EDGE_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER,&lt;br /&gt;
        WELL2_TOP:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER,&lt;br /&gt;
        WELL2_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER + FRAME.GAP,&lt;br /&gt;
        FRAME2_SURFACE_TOP:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER + FRAME.GAP,&lt;br /&gt;
        FRAME2_SURFACE_BOTTOM:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER + FRAME.GAP + FRAME.FRAME_FACE,&lt;br /&gt;
        ROW_HEIGHT:FRAME.BORDER + FRAME.FRAME_FACE + FRAME.GAP + FRAME.BORDER + FRAME.BUTTON_FACE + FRAME.BORDER + FRAME.GAP + FRAME.FRAME_FACE + FRAME.BORDER,&lt;br /&gt;
        OVERLAP:FRAME.FRAME_FACE + FRAME.GAP&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /* 기존 소비 파일의 이름은 정본 좌표에 대한 별칭으로만 유지한다. */&lt;br /&gt;
    DIMENSIONS.TOP_FRAME = DIMENSIONS.FRAME_SURFACE_BOTTOM;&lt;br /&gt;
    DIMENSIONS.BUTTON_TOP = DIMENSIONS.BUTTON_EDGE_TOP;&lt;br /&gt;
    DIMENSIONS.BUTTON_BOTTOM = DIMENSIONS.BUTTON_EDGE_BOTTOM;&lt;br /&gt;
    DIMENSIONS.BOTTOM_FRAME = DIMENSIONS.ROW_HEIGHT - DIMENSIONS.FRAME2_SURFACE_TOP;&lt;br /&gt;
    DIMENSIONS.BUTTON_TOTAL = DIMENSIONS.BUTTON_EDGE_BOTTOM - DIMENSIONS.BUTTON_EDGE_TOP;&lt;br /&gt;
    var listeners = [];&lt;br /&gt;
&lt;br /&gt;
    DIMENSIONS.BOTTOM_START_ABS_Y = DIMENSIONS.ROW_HEIGHT - DIMENSIONS.OVERLAP;&lt;br /&gt;
&lt;br /&gt;
    function integer(value) {&lt;br /&gt;
        return Math.round(Number(value) || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function snapWidth(raw) {&lt;br /&gt;
        return Math.floor(Number(raw) || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function slantX(xTop, y, dir, phase) {&lt;br /&gt;
        phase = phase || 0;&lt;br /&gt;
        return xTop + dir * Math.floor((y + phase) / FRAME.SLANT_RISE) * FRAME.SLANT_RUN;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function segment(leftTopX, rightTopX, height, leftDir, rightDir, phase) {&lt;br /&gt;
        return [&lt;br /&gt;
            [integer(leftTopX), 0],&lt;br /&gt;
            [integer(rightTopX), 0],&lt;br /&gt;
            [integer(slantX(rightTopX, height, rightDir, phase)), integer(height)],&lt;br /&gt;
            [integer(slantX(leftTopX, height, leftDir, phase)), integer(height)]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function segmentAt(leftTopX, rightTopX, topY, bottomY, leftDir, rightDir, phase, leftInset, rightInset) {&lt;br /&gt;
        leftInset = integer(leftInset || 0);&lt;br /&gt;
        rightInset = integer(rightInset || 0);&lt;br /&gt;
        return [&lt;br /&gt;
            [integer(slantX(leftTopX, topY, leftDir, phase) + leftInset), integer(topY)],&lt;br /&gt;
            [integer(slantX(rightTopX, topY, rightDir, phase) - rightInset), integer(topY)],&lt;br /&gt;
            [integer(slantX(rightTopX, bottomY, rightDir, phase) - rightInset), integer(bottomY)],&lt;br /&gt;
            [integer(slantX(leftTopX, bottomY, leftDir, phase) + leftInset), integer(bottomY)]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rowAt(leftTopX, rightTopX, y, leftDir, rightDir, phase, leftInset, rightInset) {&lt;br /&gt;
        leftInset = integer(leftInset || 0);&lt;br /&gt;
        rightInset = integer(rightInset || 0);&lt;br /&gt;
        return [&lt;br /&gt;
            [integer(slantX(leftTopX, y, leftDir, phase) + leftInset), integer(y)],&lt;br /&gt;
            [integer(slantX(rightTopX, y, rightDir, phase) - rightInset), integer(y)]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function pts(points) {&lt;br /&gt;
        return points.map(function (point) {&lt;br /&gt;
            return integer(point[0]) + &#039;,&#039; + integer(point[1]);&lt;br /&gt;
        }).join(&#039; &#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function create(name, className) {&lt;br /&gt;
        var node = document.createElementNS(SVG_NS, name);&lt;br /&gt;
        if (className) node.setAttribute(&#039;class&#039;, className);&lt;br /&gt;
        node.setAttribute(&#039;shape-rendering&#039;, &#039;crispEdges&#039;);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function polygon(className, points) {&lt;br /&gt;
        var node = create(&#039;polygon&#039;, className);&lt;br /&gt;
        node.setAttribute(&#039;points&#039;, pts(points));&lt;br /&gt;
        node.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function line(className, start, end) {&lt;br /&gt;
        var node = create(&#039;line&#039;, className);&lt;br /&gt;
        node.setAttribute(&#039;x1&#039;, integer(start[0]));&lt;br /&gt;
        node.setAttribute(&#039;y1&#039;, integer(start[1]));&lt;br /&gt;
        node.setAttribute(&#039;x2&#039;, integer(end[0]));&lt;br /&gt;
        node.setAttribute(&#039;y2&#039;, integer(end[1]));&lt;br /&gt;
        node.setAttribute(&#039;vector-effect&#039;, &#039;non-scaling-stroke&#039;);&lt;br /&gt;
        node.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rect(className, x, y, width, height) {&lt;br /&gt;
        var node = create(&#039;rect&#039;, className);&lt;br /&gt;
        node.setAttribute(&#039;x&#039;, integer(x));&lt;br /&gt;
        node.setAttribute(&#039;y&#039;, integer(y));&lt;br /&gt;
        node.setAttribute(&#039;width&#039;, Math.max(0, integer(width)));&lt;br /&gt;
        node.setAttribute(&#039;height&#039;, Math.max(0, integer(height)));&lt;br /&gt;
        node.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function centerX(points) {&lt;br /&gt;
        return integer((points[0][0] + points[1][0] + points[2][0] + points[3][0]) / 4);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readWidth(node) {&lt;br /&gt;
        if (!node) return 0;&lt;br /&gt;
        return snapWidth(node.clientWidth || node.offsetWidth || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setSeams(next) {&lt;br /&gt;
        var frame = w.CLBI.frame;&lt;br /&gt;
        var copy = {};&lt;br /&gt;
&lt;br /&gt;
        Object.keys(next || {}).forEach(function (key) {&lt;br /&gt;
            copy[key] = next[key];&lt;br /&gt;
        });&lt;br /&gt;
        copy.revision = integer((frame.seams &amp;amp;&amp;amp; frame.seams.revision) || 0) + 1;&lt;br /&gt;
        frame.seams = copy;&lt;br /&gt;
&lt;br /&gt;
        listeners.slice().forEach(function (listener) {&lt;br /&gt;
            try { listener(copy); } catch (error) {}&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            document.dispatchEvent(new CustomEvent(&#039;framegeometryseams&#039;, { detail:copy }));&lt;br /&gt;
        } catch (error) {}&lt;br /&gt;
&lt;br /&gt;
        if (w.mw &amp;amp;&amp;amp; w.mw.hook) {&lt;br /&gt;
            w.mw.hook(&#039;frame.geometry.seams&#039;).fire(copy);&lt;br /&gt;
        }&lt;br /&gt;
        return copy;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function onSeams(listener) {&lt;br /&gt;
        if (typeof listener !== &#039;function&#039;) return function () {};&lt;br /&gt;
        listeners.push(listener);&lt;br /&gt;
        if (w.CLBI.frame.seams &amp;amp;&amp;amp; w.CLBI.frame.seams.width) {&lt;br /&gt;
            listener(w.CLBI.frame.seams);&lt;br /&gt;
        }&lt;br /&gt;
        return function () {&lt;br /&gt;
            var index = listeners.indexOf(listener);&lt;br /&gt;
            if (index !== -1) listeners.splice(index, 1);&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    w.CLBI = w.CLBI || {};&lt;br /&gt;
    w.CLBI.frame = {&lt;br /&gt;
        FRAME:FRAME,&lt;br /&gt;
        DIMENSIONS:DIMENSIONS,&lt;br /&gt;
        slantX:slantX,&lt;br /&gt;
        segment:segment,&lt;br /&gt;
        segmentAt:segmentAt,&lt;br /&gt;
        rowAt:rowAt,&lt;br /&gt;
        pts:pts,&lt;br /&gt;
        create:create,&lt;br /&gt;
        polygon:polygon,&lt;br /&gt;
        line:line,&lt;br /&gt;
        rect:rect,&lt;br /&gt;
        centerX:centerX,&lt;br /&gt;
        snapWidth:snapWidth,&lt;br /&gt;
        readWidth:readWidth,&lt;br /&gt;
        setSeams:setSeams,&lt;br /&gt;
        onSeams:onSeams,&lt;br /&gt;
        seams:{}&lt;br /&gt;
    };&lt;br /&gt;
}(window, document));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4677</id>
		<title>미디어위키:Common.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4677"/>
		<updated>2026-07-17T04:00:18Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-pixel-stack-20260717 / js/Common.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;mw.loader.load(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* Raw MediaWiki script cache key.&lt;br /&gt;
   This is not a semantic version. It only prevents stale raw JS during active editing. */&lt;br /&gt;
window.CLBI_RAW_LOAD_BUST = &#039;portal-frame-pixel-spec-20260717-003&#039;;&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Raw script loads are tracked so the site boot gate can wait for real subsystem scripts&lt;br /&gt;
before releasing the normal surface.  The old prefixed load-bust variable remains for&lt;br /&gt;
compatibility only; new loader state uses unprefixed EntryScriptLoads/EntryRawScriptPromises.&lt;br /&gt;
*/&lt;br /&gt;
window.EntryScriptLoads = window.EntryScriptLoads || [];&lt;br /&gt;
window.EntryRawScriptPromises = window.EntryRawScriptPromises || {};&lt;br /&gt;
function buildEntryRawScriptUrl(key) {&lt;br /&gt;
    var url = &#039;/index.php?title=&#039; + encodeURIComponent(key) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;;&lt;br /&gt;
    var token;&lt;br /&gt;
    var sep;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, key);&lt;br /&gt;
        }&lt;br /&gt;
        if (/[?&amp;amp;]_entryRev=/.test(url)) return url;&lt;br /&gt;
        token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(&#039;raw-script-fallback&#039;) : window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        token = window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
    return url + sep + &#039;v=&#039; + encodeURIComponent(String(token || &#039;entry-raw&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadClbiRawScript(title) {&lt;br /&gt;
    var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
    var promise;&lt;br /&gt;
&lt;br /&gt;
    if (!key) return Promise.resolve(false);&lt;br /&gt;
    if (window.EntryRawScriptPromises[key]) return window.EntryRawScriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
    promise = Promise.resolve()&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.ensureLoaded === &#039;function&#039;) {&lt;br /&gt;
                return window.RevisionManifest.ensureLoaded();&lt;br /&gt;
            }&lt;br /&gt;
            return null;&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function () { return null; })&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
                script.src = buildEntryRawScriptUrl(key);&lt;br /&gt;
                script.async = true;&lt;br /&gt;
                script.setAttribute(&#039;data-entry-raw-script&#039;, key);&lt;br /&gt;
                script.onload = function () { resolve({ title: key, ok: true, src: script.src }); };&lt;br /&gt;
                script.onerror = function () { resolve({ title: key, ok: false, src: script.src }); };&lt;br /&gt;
                (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    window.EntryRawScriptPromises[key] = promise;&lt;br /&gt;
    window.EntryScriptLoads.push(promise);&lt;br /&gt;
    return promise;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Main-page fresh assets with fallback&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css의 MainPage.css import는 안전망으로 그대로 유지한다.&lt;br /&gt;
대문에서는 새로고침마다 timestamp가 붙은 CSS/JS를 추가로 불러와&lt;br /&gt;
브라우저·프록시의 오래된 raw 응답을 우회한다.&lt;br /&gt;
&lt;br /&gt;
이 보조 로더가 실패해도 기존 정적 import와 기존 화면은 남는다.&lt;br /&gt;
*/&lt;br /&gt;
window.MainPageFreshAssets = window.MainPageFreshAssets || (function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var sessionToken = String(Date.now());&lt;br /&gt;
    var ASSET_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var scriptPromises = {};&lt;br /&gt;
    var styleNode = null;&lt;br /&gt;
&lt;br /&gt;
    function isMainPage() {&lt;br /&gt;
        var page = &#039;&#039;;&lt;br /&gt;
        try {&lt;br /&gt;
            page = String(mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;);&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return page.replace(/_/g, &#039; &#039;) === &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrl(title, type) {&lt;br /&gt;
        var ctype = type === &#039;style&#039; ? &#039;text/css&#039; : &#039;text/javascript&#039;;&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) +&lt;br /&gt;
            &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype) +&lt;br /&gt;
            &#039;&amp;amp;_mainPageFresh=&#039; + encodeURIComponent(sessionToken);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadStyle() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        if (styleNode &amp;amp;&amp;amp; styleNode.parentNode) {&lt;br /&gt;
            return Promise.resolve(true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var link = document.createElement(&#039;link&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            link.rel = &#039;stylesheet&#039;;&lt;br /&gt;
            link.href = rawUrl(&#039;MediaWiki:MainPage.css&#039;, &#039;style&#039;);&lt;br /&gt;
            link.setAttribute(&#039;data-main-page-fresh-style&#039;, sessionToken);&lt;br /&gt;
            link.onload = function () {&lt;br /&gt;
                styleNode = link;&lt;br /&gt;
                settle(true);&lt;br /&gt;
            };&lt;br /&gt;
            link.onerror = function () {&lt;br /&gt;
                /* Common.css의 정적 import가 그대로 남아 있으므로 화면은 유지된다. */&lt;br /&gt;
                settle(false);&lt;br /&gt;
            };&lt;br /&gt;
            timeout = window.setTimeout(function () { settle(false); }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(link);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadScript(title) {&lt;br /&gt;
        var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!key || !isMainPage()) return Promise.resolve(false);&lt;br /&gt;
        if (scriptPromises[key]) return scriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
        scriptPromises[key] = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            script.src = rawUrl(key, &#039;script&#039;);&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-main-page-fresh-script&#039;, key);&lt;br /&gt;
            script.onload = function () { settle(true); };&lt;br /&gt;
            script.onerror = function () { settle(false); };&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return scriptPromises[key];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensure() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        return loadStyle()&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:FrameGeometry.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryPillar.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:PortalSectionNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:BottomGuideNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (window.CategoryNav &amp;amp;&amp;amp; typeof window.CategoryNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.CategoryPillar &amp;amp;&amp;amp; typeof window.CategoryPillar.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryPillar.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.PortalSectionNav &amp;amp;&amp;amp; typeof window.PortalSectionNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.PortalSectionNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.render === &#039;function&#039;) {&lt;br /&gt;
                    window.BottomGuideNav.render();&lt;br /&gt;
                }&lt;br /&gt;
                return true;&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function status() {&lt;br /&gt;
        return {&lt;br /&gt;
            mainPage: isMainPage(),&lt;br /&gt;
            token: sessionToken,&lt;br /&gt;
            style: !!document.querySelector(&#039;link[data-main-page-fresh-style]&#039;),&lt;br /&gt;
            geometry: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:FrameGeometry.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            category: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            pillar: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryPillar.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            section: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:PortalSectionNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            guide: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:BottomGuideNav.js&amp;quot;]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            ensure();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(ensure, 0);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        ensure: ensure,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Account access page marker&lt;br /&gt;
   =========================================&lt;br /&gt;
   New shared systems use unprefixed names.  The server body class differs&lt;br /&gt;
   between skins/locales, so the canonical special-page name is normalized&lt;br /&gt;
   once and exposed as a stable page-state class for the login surface.&lt;br /&gt;
*/&lt;br /&gt;
(function markAccountLoginPage(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var canonical = &#039;&#039;;&lt;br /&gt;
    var pageName = &#039;&#039;;&lt;br /&gt;
    var isLoginPage = false;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        canonical = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039; : &#039;&#039;).toLowerCase();&lt;br /&gt;
        pageName = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    isLoginPage = canonical === &#039;userlogin&#039; || /^(?:special|특수):(?:userlogin|로그인)$/.test(pageName);&lt;br /&gt;
    if (!isLoginPage) return;&lt;br /&gt;
&lt;br /&gt;
    document.documentElement.classList.add(&#039;account-login-page-root&#039;);&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountTarget(link) {&lt;br /&gt;
        var href = &#039;&#039;;&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var text = &#039;&#039;;&lt;br /&gt;
        var sample = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!link) return false;&lt;br /&gt;
        href = String(link.getAttribute(&#039;href&#039;) || &#039;&#039;);&lt;br /&gt;
        title = String(link.getAttribute(&#039;title&#039;) || &#039;&#039;);&lt;br /&gt;
        text = String(link.textContent || &#039;&#039;);&lt;br /&gt;
        try { href = decodeURIComponent(href); } catch (err) {}&lt;br /&gt;
        sample = (href + &#039; &#039; + title + &#039; &#039; + text).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        return /(?:special|특수)\s*[:%]\s*(?:createaccount|계정\s*(?:만들기|생성))|createaccount|계정\s*(?:만들기|생성)/i.test(sample);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function suppressCreateAccountSurface(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var fixedSelectors = [&lt;br /&gt;
            &#039;.mw-createacct-benefits-container&#039;,&lt;br /&gt;
            &#039;.mw-createacct-benefits-list&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-cta&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;#mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;.mw-userlogin-create&#039;&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        fixedSelectors.forEach(function (selector) {&lt;br /&gt;
            Array.prototype.forEach.call(scope.querySelectorAll(selector), function (node) {&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                node.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;a[href]&#039;), function (link) {&lt;br /&gt;
            var container;&lt;br /&gt;
            if (!isCreateAccountTarget(link)) return;&lt;br /&gt;
&lt;br /&gt;
            link.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            link.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            link.setAttribute(&#039;tabindex&#039;, &#039;-1&#039;);&lt;br /&gt;
&lt;br /&gt;
            container = link.closest(&#039;.mw-ui-vform-field, .oo-ui-fieldLayout, .mw-userlogin-create, p, li&#039;);&lt;br /&gt;
            if (container &amp;amp;&amp;amp; container.querySelectorAll(&#039;a&#039;).length === 1) {&lt;br /&gt;
                container.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                container.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyMarker() {&lt;br /&gt;
        if (!document.body) return;&lt;br /&gt;
        document.body.classList.add(&#039;account-login-page&#039;);&lt;br /&gt;
        suppressCreateAccountSurface(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, applyMarker, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyMarker();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                suppressCreateAccountSurface(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Boot gate prelude&lt;br /&gt;
   =========================================&lt;br /&gt;
   This tiny prelude runs before the full EntryStore/EntryLoader/BootGate implementation.&lt;br /&gt;
   Its job is only to guarantee that the user sees the separate loading surface before the&lt;br /&gt;
   normal wiki shell can paint.  The full BootGate later adopts the same DOM node and keeps&lt;br /&gt;
   it open until the real full-entry readiness contract is satisfied.&lt;br /&gt;
&lt;br /&gt;
   Maintenance rule:&lt;br /&gt;
   - Do not move first-screen hiding into a late subsystem callback.  If the boot gate is&lt;br /&gt;
     meant to protect the first impression, the surface must be activated before sidebars,&lt;br /&gt;
     navbars, document wells, nations panels, or other entry surfaces can visibly fill in.&lt;br /&gt;
   - New names remain unprefixed.  Existing prefixed names elsewhere are legacy aliases only.&lt;br /&gt;
*/&lt;br /&gt;
(function installBootGatePrelude(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SCREEN_ID = &#039;boot-gate-screen&#039;;&lt;br /&gt;
    var STYLE_ID = &#039;boot-gate-prelude-style&#039;;&lt;br /&gt;
    var START_TIME = Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getMwConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.mw &amp;amp;&amp;amp; window.mw.config &amp;amp;&amp;amp; typeof window.mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = window.mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDeveloperOrEditingPage() {&lt;br /&gt;
        var ns = Number(getMwConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(getMwConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(getMwConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Developer/system pages must stay practical.  Editing MediaWiki:, File:,&lt;br /&gt;
         * Project:, Template:, Module:, Category:, or any non-view action already forces&lt;br /&gt;
         * a full page load in MediaWiki, so showing the entry boot screen there only slows&lt;br /&gt;
         * maintenance work and does not improve the public first impression.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = getMwConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(getMwConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(getMwConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var anonymousUser = isAnonymousUser();&lt;br /&gt;
    var disabled = isAuthenticationPage() || (!anonymousUser &amp;amp;&amp;amp; (hasQueryFlag(&#039;bootGate&#039;, &#039;0&#039;) || isDeveloperOrEditingPage()));&lt;br /&gt;
&lt;br /&gt;
    function injectStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (disabled || document.getElementById(STYLE_ID)) return;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = STYLE_ID;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active,body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt;:not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen{position:fixed!important;inset:0!important;z-index:2147483000!important;display:flex!important;align-items:center!important;justify-content:center!important;background:#080808!important;color:#d8d8d8!important;opacity:1;pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-decoration-layer{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-panel{position:relative;z-index:2;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-close{display:none;position:absolute;right:7px;top:6px;z-index:3;height:18px;min-width:18px;border:1px solid #000;background:#141414;color:#ddd;font-size:11px;line-height:16px;padding:0 5px;cursor:pointer;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview{inset:auto!important;left:18px!important;top:18px!important;width:min(720px,calc(100vw - 380px))!important;height:360px!important;z-index:99990!important;overflow:hidden!important;border:1px solid #000!important;box-shadow:0 10px 28px rgba(0,0,0,.55)!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-panel,#boot-gate-screen.is-preview .boot-gate-close{pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-close{display:block;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activate() {&lt;br /&gt;
        if (disabled) return;&lt;br /&gt;
        injectStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeNode() {&lt;br /&gt;
        var node;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var status;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var fill;&lt;br /&gt;
        var progress;&lt;br /&gt;
        var detail;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
&lt;br /&gt;
        if (disabled || !document.body) return null;&lt;br /&gt;
        activate();&lt;br /&gt;
&lt;br /&gt;
        node = document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) return node;&lt;br /&gt;
&lt;br /&gt;
        node = document.createElement(&#039;div&#039;);&lt;br /&gt;
        node.id = SCREEN_ID;&lt;br /&gt;
        node.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
        node.setAttribute(&#039;data-boot-gate-prelude&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        title.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        status = document.createElement(&#039;div&#039;);&lt;br /&gt;
        status.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        status.textContent = &#039;Preparing site entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        fill = document.createElement(&#039;div&#039;);&lt;br /&gt;
        fill.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(fill);&lt;br /&gt;
&lt;br /&gt;
        progress = document.createElement(&#039;div&#039;);&lt;br /&gt;
        progress.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        progress.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        detail = document.createElement(&#039;div&#039;);&lt;br /&gt;
        detail.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        detail.textContent = &#039;boot gate prelude&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { release(node); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(title);&lt;br /&gt;
        panel.appendChild(status);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(progress);&lt;br /&gt;
        panel.appendChild(detail);&lt;br /&gt;
        node.appendChild(decoLayer);&lt;br /&gt;
        node.appendChild(panel);&lt;br /&gt;
        node.appendChild(close);&lt;br /&gt;
&lt;br /&gt;
        document.body.insertBefore(node, document.body.firstChild || null);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function onBody(callback) {&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            callback();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, callback, { once: true });&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function tick() {&lt;br /&gt;
            if (document.body) return callback();&lt;br /&gt;
            window.setTimeout(tick, 10);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function release(node) {&lt;br /&gt;
        node = node || document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) {&lt;br /&gt;
            node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
            node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            }, 240);&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (document.documentElement) document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    activate();&lt;br /&gt;
    onBody(function () {&lt;br /&gt;
        activate();&lt;br /&gt;
        makeNode();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.__BootGatePrelude = {&lt;br /&gt;
        startTime: START_TIME,&lt;br /&gt;
        activate: activate,&lt;br /&gt;
        ensure: makeNode,&lt;br /&gt;
        release: release,&lt;br /&gt;
        node: function () { return document.getElementById(SCREEN_ID); },&lt;br /&gt;
        disabled: disabled&lt;br /&gt;
    };&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Site boot gate and entry artifact contract&lt;br /&gt;
   =========================================&lt;br /&gt;
   This is the initial-load full/half entry artifact system.&lt;br /&gt;
&lt;br /&gt;
   Purpose:&lt;br /&gt;
   - The loading screen is not decorative. It exists only during the first site entry in a&lt;br /&gt;
     tab, before the user is allowed into the normal wiki surface.&lt;br /&gt;
   - A &amp;quot;full&amp;quot; entry must mean that the first view of that system can appear without an&lt;br /&gt;
     additional visible data load. For the nations system, the 1950 entry is full only when&lt;br /&gt;
     its nation list/link-map data and first-view pixel decorations are ready in this tab.&lt;br /&gt;
   - A &amp;quot;half&amp;quot; entry is a predictive warm state for the next likely path. It may fetch and&lt;br /&gt;
     parse data, but may skip expensive final work such as image/canvas preparation until it&lt;br /&gt;
     is promoted to full.&lt;br /&gt;
   - This principle is a site-wide design priority, like SPA continuity. New viewers,&lt;br /&gt;
     document systems, and information panels should define their entry full/half packs&lt;br /&gt;
     before exposing a first screen that can visibly fill in later.&lt;br /&gt;
   - SPA navigation is a consumer phase, not a blocking phase.  Do not add BootGate holds&lt;br /&gt;
     to SPA route changes.  If a route needs seamless first paint, prepare its artifacts&lt;br /&gt;
     during the initial boot pack and have the subsystem consume EntryStore synchronously&lt;br /&gt;
     before inserting or painting visible DOM.&lt;br /&gt;
&lt;br /&gt;
   Naming rule:&lt;br /&gt;
   - New public APIs, globals, classes, functions, files, and components must not use a&lt;br /&gt;
     project prefix. Old prefixed globals are legacy compatibility surfaces only.&lt;br /&gt;
   - New code should use names such as BootGate, EntryLoader, EntryStore, and boot-gate-*.&lt;br /&gt;
*/&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var MANIFEST_TITLE = &#039;MediaWiki:EntryManifest.json&#039;;&lt;br /&gt;
    var BUILD_ID = &#039;20260711-existing-account-login-001&#039;;&lt;br /&gt;
    var deferredEntryWarmups = [];&lt;br /&gt;
    var READY_KEY = &#039;boot-gate-ready-version&#039;;&lt;br /&gt;
    var DISMISS_PARAM = &#039;bootGate&#039;;&lt;br /&gt;
    var bootStarted = false;&lt;br /&gt;
    var bootPromise = null;&lt;br /&gt;
    var bootNode = null;&lt;br /&gt;
    var bootStatusNode = null;&lt;br /&gt;
    var bootProgressNode = null;&lt;br /&gt;
    var bootDetailNode = null;&lt;br /&gt;
    var bootFillNode = null;&lt;br /&gt;
    var bootStartTime = 0;&lt;br /&gt;
    var earlyBootStyleInjected = false;&lt;br /&gt;
    var loginGateLocked = false;&lt;br /&gt;
    var loginGateActionNode = null;&lt;br /&gt;
&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.config &amp;amp;&amp;amp; typeof mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBootPageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        return canonical === &#039;createaccount&#039; || /^(?:special|특수):(?:createaccount|계정 ?(?:만들기|생성))/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = readConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(readConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requiresLoginGate() {&lt;br /&gt;
        return isAnonymousUser() &amp;amp;&amp;amp; !isAuthenticationPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBootExcludedPage() {&lt;br /&gt;
        var ns = Number(readConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(readConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(readConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * The public boot gate is for normal reading surfaces.  Developer/system&lt;br /&gt;
         * namespaces and edit/diff/history actions already reload outside SPA, so&lt;br /&gt;
         * blocking them would turn every maintenance save into another entry boot.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var LOGIN_REQUIRED = requiresLoginGate();&lt;br /&gt;
    var BOOT_EXCLUDED_PAGE = isBootExcludedPage() &amp;amp;&amp;amp; !LOGIN_REQUIRED;&lt;br /&gt;
&lt;br /&gt;
    function injectEarlyBootStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (earlyBootStyleInjected || !document.documentElement) return;&lt;br /&gt;
        earlyBootStyleInjected = true;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = &#039;boot-gate-early-style&#039;;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active, body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt; :not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBootSurface() {&lt;br /&gt;
        injectEarlyBootStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!BOOT_EXCLUDED_PAGE) activateBootSurface();&lt;br /&gt;
    else if (window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.release) window.__BootGatePrelude.release();&lt;br /&gt;
&lt;br /&gt;
    var defaultManifest = {&lt;br /&gt;
        version: &#039;20260710-globe-vhs-restore-entry-001&#039;,&lt;br /&gt;
        boot: {&lt;br /&gt;
            minDisplayMs: 950,&lt;br /&gt;
            cachedMinDisplayMs: 350,&lt;br /&gt;
            maxBlockingMs: 15000&lt;br /&gt;
        },&lt;br /&gt;
        initial: {&lt;br /&gt;
            full: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;decorations-registry&#039;,&lt;br /&gt;
                    label: &#039;DECORATION REGISTRY&#039;,&lt;br /&gt;
                    type: &#039;decorations&#039;,&lt;br /&gt;
                    ref: &#039;MediaWiki:Decorations.json&#039;,&lt;br /&gt;
                    page: &#039;시대&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    preparePixels: true&lt;br /&gt;
                },&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1950-entry&#039;,&lt;br /&gt;
                    label: &#039;1950 ERA ENTRY&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    level: &#039;full&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ],&lt;br /&gt;
            half: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1960-half&#039;,&lt;br /&gt;
                    label: &#039;1960 ERA HALF&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1960&#039;,&lt;br /&gt;
                    level: &#039;half&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ]&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function now() {&lt;br /&gt;
        return Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var BootPerf = window.BootPerf = window.BootPerf || (function () {&lt;br /&gt;
        var t0 = now();&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;entry&#039;), start: now(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = now();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = ended - item.start;&lt;br /&gt;
            item.offset = item.start - t0;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            entries.push(item);&lt;br /&gt;
            return item;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function instant(name, meta) {&lt;br /&gt;
            var t = now();&lt;br /&gt;
            entries.push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: t - t0, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measure(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function rows() {&lt;br /&gt;
            return entries.slice().sort(function (a, b) { return a.start - b.start; }).map(function (item) {&lt;br /&gt;
                return {&lt;br /&gt;
                    offset: item.offset,&lt;br /&gt;
                    ms: item.ms,&lt;br /&gt;
                    name: item.name,&lt;br /&gt;
                    meta: item.meta || {}&lt;br /&gt;
                };&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            var total = list.reduce(function (max, item) { return Math.max(max, item.offset + item.ms); }, 0);&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD_ID,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: total,&lt;br /&gt;
                entries: list,&lt;br /&gt;
                active: Object.keys(active).map(function (id) { return active[id]; })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function print() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            if (!window.console || !console.log) return summary();&lt;br /&gt;
            try {&lt;br /&gt;
                console.groupCollapsed(&#039;[BootPerf] entry loading timeline · &#039; + BUILD_ID);&lt;br /&gt;
                if (console.table) console.table(list.map(function (item) {&lt;br /&gt;
                    return {&lt;br /&gt;
                        offset: item.offset + &#039;ms&#039;,&lt;br /&gt;
                        duration: item.ms + &#039;ms&#039;,&lt;br /&gt;
                        name: item.name,&lt;br /&gt;
                        detail: JSON.stringify(item.meta || {})&lt;br /&gt;
                    };&lt;br /&gt;
                }));&lt;br /&gt;
                else list.forEach(function (item) { console.log(item.offset + &#039;ms&#039;, item.ms + &#039;ms&#039;, item.name, item.meta || {}); });&lt;br /&gt;
                console.groupEnd();&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            return summary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: instant,&lt;br /&gt;
            measure: measure,&lt;br /&gt;
            rows: rows,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            print: print&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var InteractionPerf = window.InteractionPerf = window.InteractionPerf || (function () {&lt;br /&gt;
        var BUILD = &#039;20260710-interaction-perf-instrument-001&#039;;&lt;br /&gt;
        var t0 = (window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now());&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
        var maxEntries = 1600;&lt;br /&gt;
&lt;br /&gt;
        function perfNow() {&lt;br /&gt;
            return window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function push(entry) {&lt;br /&gt;
            entries.push(entry);&lt;br /&gt;
            if (entries.length &amp;gt; maxEntries) entries.splice(0, entries.length - maxEntries);&lt;br /&gt;
            return entry;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;interaction&#039;), start: perfNow(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = perfNow();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = Math.round((ended - item.start) * 100) / 100;&lt;br /&gt;
            item.offset = Math.round((item.start - t0) * 100) / 100;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            return push(item);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function mark(name, meta) {&lt;br /&gt;
            var t = perfNow();&lt;br /&gt;
            return push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: Math.round((t - t0) * 100) / 100, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureSync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                var value = fn();&lt;br /&gt;
                end(id, { ok: true });&lt;br /&gt;
                return value;&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                throw err;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureAsync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: Math.round((perfNow() - t0) * 100) / 100,&lt;br /&gt;
                entries: entries.slice().sort(function (a, b) {&lt;br /&gt;
                    if (a.offset !== b.offset) return a.offset - b.offset;&lt;br /&gt;
                    return b.ms - a.ms;&lt;br /&gt;
                }),&lt;br /&gt;
                active: Object.keys(active).map(function (id) {&lt;br /&gt;
                    var item = active[id];&lt;br /&gt;
                    return { offset: Math.round((item.start - t0) * 100) / 100, ms: Math.round((perfNow() - item.start) * 100) / 100, name: item.name, meta: item.meta || {} };&lt;br /&gt;
                })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function table() {&lt;br /&gt;
            var data = summary().entries.map(function (entry) {&lt;br /&gt;
                return { offset: entry.offset, ms: entry.ms, name: entry.name, meta: entry.meta };&lt;br /&gt;
            });&lt;br /&gt;
            if (window.console &amp;amp;&amp;amp; console.table) console.table(data);&lt;br /&gt;
            return data;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.PerformanceObserver &amp;amp;&amp;amp; !window.CLBI_InteractionLongTaskObserverBound) {&lt;br /&gt;
                window.CLBI_InteractionLongTaskObserverBound = true;&lt;br /&gt;
                new PerformanceObserver(function (list) {&lt;br /&gt;
                    list.getEntries().forEach(function (entry) {&lt;br /&gt;
                        mark(&#039;browser long task&#039;, {&lt;br /&gt;
                            ms: Math.round(entry.duration * 100) / 100,&lt;br /&gt;
                            start: Math.round(entry.startTime * 100) / 100,&lt;br /&gt;
                            attribution: entry.attribution &amp;amp;&amp;amp; entry.attribution.length ? entry.attribution.length : 0&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }).observe({ entryTypes: [&#039;longtask&#039;] });&lt;br /&gt;
            }&lt;br /&gt;
        } catch (ignoreLongTaskObserver) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD,&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: mark,&lt;br /&gt;
            measureSync: measureSync,&lt;br /&gt;
            measureAsync: measureAsync,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            table: table&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
    function toArray(value) {&lt;br /&gt;
        return Array.prototype.slice.call(value || []);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function unique(list) {&lt;br /&gt;
        var seen = {};&lt;br /&gt;
        var out = [];&lt;br /&gt;
        (list || []).forEach(function (item) {&lt;br /&gt;
            item = String(item || &#039;&#039;).trim();&lt;br /&gt;
            if (!item || seen[item]) return;&lt;br /&gt;
            seen[item] = true;&lt;br /&gt;
            out.push(item);&lt;br /&gt;
        });&lt;br /&gt;
        return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasBootParam(value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + DISMISS_PARAM + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeTitle(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;#&#039;)[0]&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function extractTitleFromUrl(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;);&lt;br /&gt;
        var match = text.match(/[?&amp;amp;]title=([^&amp;amp;]+)/i);&lt;br /&gt;
        if (match) return normalizeTitle(decodeURIComponent(match[1].replace(/\+/g, &#039; &#039;)));&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeRefKey(ref) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        title = extractTitleFromUrl(text);&lt;br /&gt;
        if (title) return &#039;title:&#039; + title.toLowerCase();&lt;br /&gt;
        if (text.indexOf(&#039;/&#039;) === -1 &amp;amp;&amp;amp; text.indexOf(&#039;:&#039;) !== -1) return &#039;title:&#039; + normalizeTitle(text).toLowerCase();&lt;br /&gt;
        return &#039;url:&#039; + text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrlForRef(ref, ctype) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(text) || text.charAt(0) === &#039;/&#039;) return text;&lt;br /&gt;
        title = normalizeTitle(text.indexOf(&#039;:&#039;) !== -1 ? text : (&#039;MediaWiki:&#039; + text));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            return mw.util.getUrl(title, { action: &#039;raw&#039;, ctype: ctype || &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype || &#039;application/json&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getApiEndpoint() {&lt;br /&gt;
        return (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.wikiScript === &#039;function&#039;) ? mw.util.wikiScript(&#039;api&#039;) : &#039;/api.php&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchApi(params) {&lt;br /&gt;
        var body = new URLSearchParams();&lt;br /&gt;
        Object.keys(params || {}).forEach(function (key) {&lt;br /&gt;
            body.append(key, params[key]);&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(getApiEndpoint(), {&lt;br /&gt;
            method: &#039;POST&#039;,&lt;br /&gt;
            credentials: &#039;same-origin&#039;,&lt;br /&gt;
            headers: { &#039;Content-Type&#039;: &#039;application/x-www-form-urlencoded; charset=UTF-8&#039; },&lt;br /&gt;
            body: body.toString()&lt;br /&gt;
        }).then(function (res) {&lt;br /&gt;
            if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
            return res.json();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =========================================&lt;br /&gt;
       Revision manifest and persistent entry cache&lt;br /&gt;
       =========================================&lt;br /&gt;
       The first boot still prepares current-tab artifacts, but freshness is not guessed from&lt;br /&gt;
       filenames or old localStorage flags.  The client reads a tiny server-side current-state&lt;br /&gt;
       manifest, compares page revisions / file sha1 values, and only keeps cached raw resources&lt;br /&gt;
       whose revision token still matches the server.  The manifest is a latest-state table, not&lt;br /&gt;
       an append-only client log.&lt;br /&gt;
    */&lt;br /&gt;
    var REVISION_MANIFEST_ACTION = &#039;entryrevisionmanifest&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_LOCAL_KEY = &#039;entry-revision-manifest-current-v1&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_PRUNE_LOCAL_KEY = &#039;entry-revision-manifest-last-client-prune-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_NAME = &#039;entry-cache-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_INDEX_KEY = &#039;entry-cache-index-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_REQUEST_PREFIX = &#039;/__entry-cache__/&#039;;&lt;br /&gt;
    var ENTRY_CACHE_PACK_KEY = &#039;entry-cache-pack-state-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
    function hasCacheStorage() {&lt;br /&gt;
        return !!(window.caches &amp;amp;&amp;amp; typeof window.caches.open === &#039;function&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function safeJsonParse(text, fallback) {&lt;br /&gt;
        try { return JSON.parse(text); } catch (err) { return fallback; }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readLocalJson(key, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            var text = window.localStorage ? window.localStorage.getItem(key) : null;&lt;br /&gt;
            return text ? safeJsonParse(text, fallback) : fallback;&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return fallback;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function writeLocalJson(key, value) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.localStorage) window.localStorage.setItem(key, JSON.stringify(value));&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeManifestTitle(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var match;&lt;br /&gt;
        var i;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        for (i = 0; i &amp;lt; 3; i += 1) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
            } catch (err) { break; }&lt;br /&gt;
        }&lt;br /&gt;
        match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
        if (match) text = match[1];&lt;br /&gt;
        text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^\/+/, &#039;&#039;)&lt;br /&gt;
            .replace(/^index\.php\/?/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^wiki\/?/i, &#039;&#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
        match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/([^?#]+)(?:[?#].*)?$/i);&lt;br /&gt;
        if (match) text = &#039;File:&#039; + match[1];&lt;br /&gt;
        text = text.split(&#039;#&#039;)[0].replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
        text = text.replace(/^(?:파일|Image|이미지)\s*:/i, &#039;File:&#039;);&lt;br /&gt;
        if (/^(?:mediawiki|template|module|manage|file)\s*:/i.test(text)) {&lt;br /&gt;
            text = text.replace(/^([^:]+)\s*:\s*/, function (_, ns) { return ns.charAt(0).toUpperCase() + ns.slice(1).toLowerCase() + &#039;:&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
        if (/^File:/i.test(text)) text = &#039;File:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        if (/^Mediawiki:/i.test(text)) text = &#039;MediaWiki:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        return text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resourceToken(resource) {&lt;br /&gt;
        if (!resource || typeof resource !== &#039;object&#039;) return &#039;&#039;;&lt;br /&gt;
        return String(resource.revision || resource.sha1 || resource.hash || resource.timestamp || resource.updatedAt || resource.url || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createEntryCache() {&lt;br /&gt;
        var index = readLocalJson(ENTRY_CACHE_INDEX_KEY, { entries: {} }) || { entries: {} };&lt;br /&gt;
        var packs = readLocalJson(ENTRY_CACHE_PACK_KEY, { packs: {} }) || { packs: {} };&lt;br /&gt;
        var objectUrls = {};&lt;br /&gt;
        var stats = {&lt;br /&gt;
            textHits: 0,&lt;br /&gt;
            blobHits: 0,&lt;br /&gt;
            misses: 0,&lt;br /&gt;
            networkStores: 0,&lt;br /&gt;
            stores: 0,&lt;br /&gt;
            deletes: 0&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        function ensureIndex() {&lt;br /&gt;
            if (!index || typeof index !== &#039;object&#039;) index = { entries: {} };&lt;br /&gt;
            if (!index.entries || typeof index.entries !== &#039;object&#039;) index.entries = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactIndexForLocalStorage() {&lt;br /&gt;
            var entries;&lt;br /&gt;
            var next = {};&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var entry = entries[key] || {};&lt;br /&gt;
                var kind = String(entry.kind || &#039;raw&#039;).toLowerCase();&lt;br /&gt;
                /*&lt;br /&gt;
                 * CacheStorage already owns image/blob bodies.  Keeping one localStorage&lt;br /&gt;
                 * metadata row per flag/blob competes with MediaWiki ResourceLoader&#039;s own&lt;br /&gt;
                 * localStorage module store and can trigger QuotaExceededError before our&lt;br /&gt;
                 * code even runs on the next page load.  Text/json/file URL rows stay indexed&lt;br /&gt;
                 * because they are small and useful for invalidation diagnostics.&lt;br /&gt;
                 */&lt;br /&gt;
                if (kind === &#039;image&#039; || kind === &#039;blob&#039;) return;&lt;br /&gt;
                next[key] = entry;&lt;br /&gt;
            });&lt;br /&gt;
            index.entries = next;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensurePacks() {&lt;br /&gt;
            if (!packs || typeof packs !== &#039;object&#039;) packs = { packs: {} };&lt;br /&gt;
            if (!packs.packs || typeof packs.packs !== &#039;object&#039;) packs.packs = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function saveIndex() {&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            compactIndexForLocalStorage();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_INDEX_KEY, index);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function savePacks() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_PACK_KEY, packs);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function requestForKey(key) {&lt;br /&gt;
            return new Request(ENTRY_CACHE_REQUEST_PREFIX + encodeURIComponent(String(key || &#039;&#039;)), { credentials: &#039;same-origin&#039; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function openCache() {&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return window.caches.open(ENTRY_CACHE_NAME).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheEntry(key, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            index.entries[key] = Object.assign({}, index.entries[key] || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                resourceKey: String(meta &amp;amp;&amp;amp; meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                token: String(meta &amp;amp;&amp;amp; meta.token || &#039;&#039;),&lt;br /&gt;
                kind: String(meta &amp;amp;&amp;amp; meta.kind || &#039;raw&#039;),&lt;br /&gt;
                contentType: String(meta &amp;amp;&amp;amp; meta.contentType || &#039;&#039;),&lt;br /&gt;
                size: Number(meta &amp;amp;&amp;amp; meta.size || 0) || null,&lt;br /&gt;
                createdAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].createdAt ? index.entries[key].createdAt : now(),&lt;br /&gt;
                updatedAt: now(),&lt;br /&gt;
                lastHitAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].lastHitAt ? index.entries[key].lastHitAt : null,&lt;br /&gt;
                hits: Number(index.entries[key] &amp;amp;&amp;amp; index.entries[key].hits || 0)&lt;br /&gt;
            });&lt;br /&gt;
            saveIndex();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function markHit(key, field) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            if (index.entries[key]) {&lt;br /&gt;
                index.entries[key].hits = Number(index.entries[key].hits || 0) + 1;&lt;br /&gt;
                index.entries[key].lastHitAt = now();&lt;br /&gt;
                saveIndex();&lt;br /&gt;
            }&lt;br /&gt;
            if (field &amp;amp;&amp;amp; stats[field] != null) stats[field] += 1;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getResponse(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return null;&lt;br /&gt;
                return cache.match(requestForKey(key)).then(function (res) {&lt;br /&gt;
                    if (!res) {&lt;br /&gt;
                        stats.misses += 1;&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }&lt;br /&gt;
                    return res;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putResponse(key, response, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !response || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var cloned;&lt;br /&gt;
                var headers;&lt;br /&gt;
                var bodyPromise;&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                cloned = response.clone();&lt;br /&gt;
                bodyPromise = cloned.blob().catch(function () { return null; });&lt;br /&gt;
                return bodyPromise.then(function (blob) {&lt;br /&gt;
                    if (!blob) return false;&lt;br /&gt;
                    headers = new Headers(response.headers || {});&lt;br /&gt;
                    if (!headers.get(&#039;Content-Type&#039;)) headers.set(&#039;Content-Type&#039;, meta.contentType || blob.type || &#039;application/octet-stream&#039;);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cache-Key&#039;, key);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Resource-Key&#039;, String(meta.resourceKey || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Revision-Token&#039;, String(meta.token || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cached-At&#039;, String(now()));&lt;br /&gt;
                    return cache.put(requestForKey(key), new Response(blob, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                        stats.stores += 1;&lt;br /&gt;
                        cacheEntry(key, {&lt;br /&gt;
                            resourceKey: meta.resourceKey,&lt;br /&gt;
                            token: meta.token,&lt;br /&gt;
                            kind: meta.kind || &#039;blob&#039;,&lt;br /&gt;
                            contentType: headers.get(&#039;Content-Type&#039;) || blob.type || &#039;&#039;,&lt;br /&gt;
                            size: blob.size&lt;br /&gt;
                        });&lt;br /&gt;
                        return true;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getText(key) {&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return null;&lt;br /&gt;
                markHit(key, &#039;textHits&#039;);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putText(key, text, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var headers;&lt;br /&gt;
                var body = String(text || &#039;&#039;);&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                headers = new Headers({&lt;br /&gt;
                    &#039;Content-Type&#039;: meta.contentType || &#039;text/plain; charset=UTF-8&#039;,&lt;br /&gt;
                    &#039;X-Entry-Cache-Key&#039;: key,&lt;br /&gt;
                    &#039;X-Entry-Resource-Key&#039;: String(meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Revision-Token&#039;: String(meta.token || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Cached-At&#039;: String(now())&lt;br /&gt;
                });&lt;br /&gt;
                return cache.put(requestForKey(key), new Response(body, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                    stats.stores += 1;&lt;br /&gt;
                    cacheEntry(key, {&lt;br /&gt;
                        resourceKey: meta.resourceKey,&lt;br /&gt;
                        token: meta.token,&lt;br /&gt;
                        kind: meta.kind || &#039;text&#039;,&lt;br /&gt;
                        contentType: headers.get(&#039;Content-Type&#039;),&lt;br /&gt;
                        size: body.length&lt;br /&gt;
                    });&lt;br /&gt;
                    return true;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getBlobUrl(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                return Promise.resolve(objectUrls[key]);&lt;br /&gt;
            }&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return &#039;&#039;;&lt;br /&gt;
                return res.blob().then(function (blob) {&lt;br /&gt;
                    if (!blob || !blob.size) return &#039;&#039;;&lt;br /&gt;
                    objectUrls[key] = URL.createObjectURL(blob);&lt;br /&gt;
                    markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                    return objectUrls[key];&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchBlobUrl(url, key, meta, options) {&lt;br /&gt;
            url = String(url || &#039;&#039;).trim();&lt;br /&gt;
            key = String(key || &#039;&#039;).trim();&lt;br /&gt;
            if (!url || !key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            return getBlobUrl(key).then(function (cachedUrl) {&lt;br /&gt;
                if (cachedUrl) return cachedUrl;&lt;br /&gt;
                return fetch(url, {&lt;br /&gt;
                    credentials: &#039;same-origin&#039;,&lt;br /&gt;
                    cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
                }).then(function (res) {&lt;br /&gt;
                    if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                    stats.networkStores += 1;&lt;br /&gt;
                    return putResponse(key, res, Object.assign({}, meta || {}, { kind: meta &amp;amp;&amp;amp; meta.kind ? meta.kind : &#039;blob&#039; })).then(function () {&lt;br /&gt;
                        return getBlobUrl(key);&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return &#039;&#039;;&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function deleteKey(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
                delete objectUrls[key];&lt;br /&gt;
            }&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                return cache.delete(requestForKey(key)).then(function (ok) {&lt;br /&gt;
                    ensureIndex();&lt;br /&gt;
                    if (index.entries &amp;amp;&amp;amp; index.entries[key]) {&lt;br /&gt;
                        delete index.entries[key];&lt;br /&gt;
                        stats.deletes += 1;&lt;br /&gt;
                        saveIndex();&lt;br /&gt;
                    }&lt;br /&gt;
                    return ok;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function invalidateResources(resourceKeys) {&lt;br /&gt;
            var map = {};&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys = [];&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            (resourceKeys || []).forEach(function (resourceKey) {&lt;br /&gt;
                resourceKey = String(resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (resourceKey) map[resourceKey] = true;&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(entries).forEach(function (cacheKey) {&lt;br /&gt;
                var resourceKey = String(entries[cacheKey] &amp;amp;&amp;amp; entries[cacheKey].resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (map[resourceKey]) keys.push(cacheKey);&lt;br /&gt;
            });&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function prune(maxAgeDays) {&lt;br /&gt;
            var days = Math.max(1, Number(maxAgeDays) || 7);&lt;br /&gt;
            var cutoff = now() - days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys;&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            keys = Object.keys(entries).filter(function (key) {&lt;br /&gt;
                return Number(entries[key] &amp;amp;&amp;amp; entries[key].createdAt || 0) &amp;lt; cutoff;&lt;br /&gt;
            });&lt;br /&gt;
            window.localStorage &amp;amp;&amp;amp; window.localStorage.setItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY, String(now()));&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function reset() {&lt;br /&gt;
            Object.keys(objectUrls).forEach(function (key) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
            });&lt;br /&gt;
            objectUrls = {};&lt;br /&gt;
            index = { entries: {} };&lt;br /&gt;
            packs = { packs: {} };&lt;br /&gt;
            saveIndex();&lt;br /&gt;
            savePacks();&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            return window.caches.delete(ENTRY_CACHE_NAME).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packReady(key, token) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            return !!(packs.packs[key] &amp;amp;&amp;amp; String(packs.packs[key].token || &#039;&#039;) === token &amp;amp;&amp;amp; packs.packs[key].ready);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setPackReady(key, token, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            packs.packs[key] = Object.assign({}, meta || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                token: token,&lt;br /&gt;
                ready: true,&lt;br /&gt;
                updatedAt: now()&lt;br /&gt;
            });&lt;br /&gt;
            savePacks();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packInfo() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            return Object.assign({}, packs.packs || {});&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function info() {&lt;br /&gt;
            var lastClientPrune = 0;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var byKind = {};&lt;br /&gt;
            try { lastClientPrune = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var kind = String(entries[key] &amp;amp;&amp;amp; entries[key].kind || &#039;raw&#039;);&lt;br /&gt;
                byKind[kind] = (byKind[kind] || 0) + 1;&lt;br /&gt;
            });&lt;br /&gt;
            return {&lt;br /&gt;
                supported: hasCacheStorage(),&lt;br /&gt;
                name: ENTRY_CACHE_NAME,&lt;br /&gt;
                entries: Object.keys(entries).length,&lt;br /&gt;
                byKind: byKind,&lt;br /&gt;
                packs: Object.keys(packs.packs || {}).length,&lt;br /&gt;
                lastClientPruneAt: lastClientPrune || null,&lt;br /&gt;
                stats: Object.assign({}, stats)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Trim legacy image/blob metadata rows produced by older builds. */&lt;br /&gt;
        try { saveIndex(); } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            getResponse: getResponse,&lt;br /&gt;
            putResponse: putResponse,&lt;br /&gt;
            getText: getText,&lt;br /&gt;
            putText: putText,&lt;br /&gt;
            getBlobUrl: getBlobUrl,&lt;br /&gt;
            fetchBlobUrl: fetchBlobUrl,&lt;br /&gt;
            invalidateResources: invalidateResources,&lt;br /&gt;
            prune: prune,&lt;br /&gt;
            reset: reset,&lt;br /&gt;
            packReady: packReady,&lt;br /&gt;
            setPackReady: setPackReady,&lt;br /&gt;
            packInfo: packInfo,&lt;br /&gt;
            info: info&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryCache = window.EntryCache || createEntryCache();&lt;br /&gt;
&lt;br /&gt;
    function createRevisionManifestService() {&lt;br /&gt;
        var current = null;&lt;br /&gt;
        var previous = readLocalJson(REVISION_MANIFEST_LOCAL_KEY, null);&lt;br /&gt;
        var loadPromise = null;&lt;br /&gt;
        var changedResources = [];&lt;br /&gt;
&lt;br /&gt;
        function unwrap(payload) {&lt;br /&gt;
            return payload &amp;amp;&amp;amp; (payload.entryrevisionmanifest || payload.revisionManifest || payload) || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourcesOf(manifest) {&lt;br /&gt;
            return manifest &amp;amp;&amp;amp; manifest.resources &amp;amp;&amp;amp; typeof manifest.resources === &#039;object&#039; ? manifest.resources : {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactRevisionManifest(manifest) {&lt;br /&gt;
            var out;&lt;br /&gt;
            var resources;&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039;) return null;&lt;br /&gt;
            out = {&lt;br /&gt;
                manifestVersion: manifest.manifestVersion || manifest.version || &#039;&#039;,&lt;br /&gt;
                version: manifest.version || manifest.manifestVersion || &#039;&#039;,&lt;br /&gt;
                generatedAt: manifest.generatedAt || &#039;&#039;,&lt;br /&gt;
                pruneDays: manifest.pruneDays,&lt;br /&gt;
                clientPruneDays: manifest.clientPruneDays,&lt;br /&gt;
                resources: {}&lt;br /&gt;
            };&lt;br /&gt;
            resources = resourcesOf(manifest);&lt;br /&gt;
            Object.keys(resources).forEach(function (title) {&lt;br /&gt;
                var src = resources[title];&lt;br /&gt;
                var dst;&lt;br /&gt;
                if (!src || typeof src !== &#039;object&#039;) return;&lt;br /&gt;
                dst = {};&lt;br /&gt;
                [&#039;revision&#039;, &#039;sha1&#039;, &#039;hash&#039;, &#039;timestamp&#039;, &#039;updatedAt&#039;, &#039;url&#039;].forEach(function (key) {&lt;br /&gt;
                    if (src[key] !== undefined &amp;amp;&amp;amp; src[key] !== null &amp;amp;&amp;amp; String(src[key]) !== &#039;&#039;) dst[key] = src[key];&lt;br /&gt;
                });&lt;br /&gt;
                out.resources[title] = dst;&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function buildLookup(manifest) {&lt;br /&gt;
            var lookup = {};&lt;br /&gt;
            Object.keys(resourcesOf(manifest)).forEach(function (title) {&lt;br /&gt;
                lookup[normalizeManifestTitle(title).toLowerCase()] = resourcesOf(manifest)[title];&lt;br /&gt;
            });&lt;br /&gt;
            return lookup;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function computeChanged(prev, next) {&lt;br /&gt;
            var prevLookup = buildLookup(prev);&lt;br /&gt;
            var nextLookup = buildLookup(next);&lt;br /&gt;
            var out = [];&lt;br /&gt;
            Object.keys(nextLookup).forEach(function (key) {&lt;br /&gt;
                if (resourceToken(prevLookup[key]) !== resourceToken(nextLookup[key])) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(prevLookup).forEach(function (key) {&lt;br /&gt;
                if (!nextLookup[key]) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            return unique(out);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function load(options) {&lt;br /&gt;
            if (loadPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return loadPromise;&lt;br /&gt;
            loadPromise = fetchApi({&lt;br /&gt;
                action: REVISION_MANIFEST_ACTION,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;&lt;br /&gt;
            }).then(function (payload) {&lt;br /&gt;
                var manifest = unwrap(payload);&lt;br /&gt;
                if (!manifest || !manifest.resources) throw new Error(&#039;invalid revision manifest&#039;);&lt;br /&gt;
                current = compactRevisionManifest(manifest) || manifest;&lt;br /&gt;
                changedResources = computeChanged(previous, current);&lt;br /&gt;
                if (changedResources.length &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.invalidateResources === &#039;function&#039;) {&lt;br /&gt;
                    window.EntryCache.invalidateResources(changedResources);&lt;br /&gt;
                }&lt;br /&gt;
                writeLocalJson(REVISION_MANIFEST_LOCAL_KEY, current);&lt;br /&gt;
                previous = current;&lt;br /&gt;
                maybePrune();&lt;br /&gt;
                return current;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                current = previous || null;&lt;br /&gt;
                return current;&lt;br /&gt;
            });&lt;br /&gt;
            return loadPromise;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensureLoaded() {&lt;br /&gt;
            return current ? Promise.resolve(current) : load();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            var lookup;&lt;br /&gt;
            if (!title || !current) return null;&lt;br /&gt;
            lookup = buildLookup(current);&lt;br /&gt;
            return lookup[title.toLowerCase()] || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenForRef(ref) {&lt;br /&gt;
            return resourceToken(resourceForRef(ref));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceKeyForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            return title ? title.toLowerCase() : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForRef(ref, type) {&lt;br /&gt;
            var resourceKey = resourceKeyForRef(ref);&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            if (!resourceKey || !token) return &#039;&#039;;&lt;br /&gt;
            return String(type || &#039;raw&#039;) + &#039;:&#039; + resourceKey + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function manifestToken(extra) {&lt;br /&gt;
            var base = current &amp;amp;&amp;amp; (current.manifestVersion || current.version || current.generatedAt || &#039;&#039;) || &#039;&#039;;&lt;br /&gt;
            return String(base || &#039;no-manifest&#039;) + (extra ? (&#039;:&#039; + String(extra)) : &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenFromUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;);&lt;br /&gt;
            var match = text.match(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=([^&amp;amp;]+)/);&lt;br /&gt;
            if (!match) return &#039;&#039;;&lt;br /&gt;
            try { return decodeURIComponent(match[1]); } catch (err) { return match[1]; }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForUrl(url, type) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var token = tokenFromUrl(text) || manifestToken(&#039;url&#039;);&lt;br /&gt;
            var normalized;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                normalized = new URL(text, window.location.href);&lt;br /&gt;
                text = normalized.pathname + (normalized.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            text = text.replace(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=[^&amp;amp;]*/g, &#039;&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            try { text = decodeURI(text); } catch (err2) {}&lt;br /&gt;
            return String(type || &#039;blob&#039;) + &#039;:url:&#039; + text.toLowerCase() + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function addRevisionParam(url, ref) {&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var sep;&lt;br /&gt;
            if (!text || !token || /[?&amp;amp;]_entryRev=/.test(text)) return text;&lt;br /&gt;
            sep = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + sep + &#039;_entryRev=&#039; + encodeURIComponent(token);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function maybePrune() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var due;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            due = !last || (now() - last &amp;gt;= days * 24 * 60 * 60 * 1000);&lt;br /&gt;
            if (due &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.prune === &#039;function&#039;) {&lt;br /&gt;
                window.EntryCache.prune(days);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function countdown() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var next;&lt;br /&gt;
            var remain;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            if (!last) return { days: days, lastClientPruneAt: null, nextClientPruneAt: null, remainingMs: 0, remainingDays: 0, due: true };&lt;br /&gt;
            next = last + days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            remain = Math.max(0, next - now());&lt;br /&gt;
            return {&lt;br /&gt;
                days: days,&lt;br /&gt;
                lastClientPruneAt: last,&lt;br /&gt;
                nextClientPruneAt: next,&lt;br /&gt;
                remainingMs: remain,&lt;br /&gt;
                remainingDays: Math.ceil(remain / (24 * 60 * 60 * 1000)),&lt;br /&gt;
                due: remain &amp;lt;= 0&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function status() {&lt;br /&gt;
            return {&lt;br /&gt;
                available: !!current,&lt;br /&gt;
                manifestVersion: current &amp;amp;&amp;amp; (current.manifestVersion || current.version || &#039;&#039;),&lt;br /&gt;
                generatedAt: current &amp;amp;&amp;amp; current.generatedAt || &#039;&#039;,&lt;br /&gt;
                changedResources: changedResources.slice(0, 50),&lt;br /&gt;
                changedCount: changedResources.length,&lt;br /&gt;
                prune: countdown(),&lt;br /&gt;
                cache: window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.info === &#039;function&#039; ? window.EntryCache.info() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            load: load,&lt;br /&gt;
            ensureLoaded: ensureLoaded,&lt;br /&gt;
            current: function () { return current; },&lt;br /&gt;
            resourceForRef: resourceForRef,&lt;br /&gt;
            tokenForRef: tokenForRef,&lt;br /&gt;
            cacheKeyForRef: cacheKeyForRef,&lt;br /&gt;
            cacheKeyForUrl: cacheKeyForUrl,&lt;br /&gt;
            manifestToken: manifestToken,&lt;br /&gt;
            resourceKeyForRef: resourceKeyForRef,&lt;br /&gt;
            addRevisionParam: addRevisionParam,&lt;br /&gt;
            maybePrune: maybePrune,&lt;br /&gt;
            countdown: countdown,&lt;br /&gt;
            status: status&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.RevisionManifest = window.RevisionManifest || createRevisionManifestService();&lt;br /&gt;
&lt;br /&gt;
    function createEntryStore() {&lt;br /&gt;
        var jsonCache = {};&lt;br /&gt;
        var flagUrlCache = {};&lt;br /&gt;
        var fileUrlCache = {};&lt;br /&gt;
        var imageReadyCache = {};&lt;br /&gt;
        var imageObjectCache = {};&lt;br /&gt;
        var imageDisplayUrlCache = {};&lt;br /&gt;
        var imagePromiseCache = {};&lt;br /&gt;
        var rawPromiseCache = {};&lt;br /&gt;
        var textCache = {};&lt;br /&gt;
        var textPromiseCache = {};&lt;br /&gt;
&lt;br /&gt;
        function fetchJsonRef(ref, options) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var promiseKey;&lt;br /&gt;
            var url;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty json ref&#039;));&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(ref, &#039;json&#039;) : &#039;&#039;;&lt;br /&gt;
            promiseKey = cacheKey ? (key + &#039;@&#039; + cacheKey) : key;&lt;br /&gt;
            if (jsonCache[promiseKey]) return Promise.resolve(jsonCache[promiseKey].data);&lt;br /&gt;
            if (rawPromiseCache[promiseKey]) return rawPromiseCache[promiseKey];&lt;br /&gt;
            url = rawUrlForRef(ref, &#039;application/json&#039;);&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
            }&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(ref) : key;&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(ref) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                rawPromiseCache[promiseKey] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        var cachedData = cachedText &amp;amp;&amp;amp; cachedText.trim() ? JSON.parse(cachedText) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: cachedData, loadedAt: now(), persistent: true };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        return cachedData;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token });&lt;br /&gt;
                        return data;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return rawPromiseCache[promiseKey];&lt;br /&gt;
            }&lt;br /&gt;
            rawPromiseCache[promiseKey] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                return data;&lt;br /&gt;
            });&lt;br /&gt;
            return rawPromiseCache[promiseKey];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getJsonSync(ref) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            return key &amp;amp;&amp;amp; jsonCache[key] ? jsonCache[key].data : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setJsonRef(ref, data) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            jsonCache[key] = { key: key, ref: ref, url: rawUrlForRef(ref, &#039;application/json&#039;), data: data, loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeFile(value) {&lt;br /&gt;
            return String(value || &#039;&#039;)&lt;br /&gt;
                .replace(/^(?:file|파일):/i, &#039;&#039;)&lt;br /&gt;
                .trim();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileKey(value) {&lt;br /&gt;
            return normalizeFile(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setFlagUrl(file, url) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            flagUrlCache[key] = String(url || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getFlagUrl(file) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            return key ? (flagUrlCache[key] || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFlagUrls(files) {&lt;br /&gt;
            var clean = unique((files || []).map(normalizeFile).filter(Boolean));&lt;br /&gt;
&lt;br /&gt;
            function cacheKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;flagurl&#039;) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function resourceKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + fileKey(file));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function tokenForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function hydrateCachedFlagUrl(file) {&lt;br /&gt;
                var key = fileKey(file);&lt;br /&gt;
                var cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                if (!key || flagUrlCache[key] !== undefined || !cacheKey || !window.EntryCache || typeof window.EntryCache.getText !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.resolve(false);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        flagUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return true;&lt;br /&gt;
                    }&lt;br /&gt;
                    return false;&lt;br /&gt;
                }).catch(function () { return false; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(clean.map(hydrateCachedFlagUrl)).then(function () {&lt;br /&gt;
                var pending = clean.filter(function (file) {&lt;br /&gt;
                    return getFlagUrl(file) === &#039;&#039; &amp;amp;&amp;amp; flagUrlCache[fileKey(file)] === undefined;&lt;br /&gt;
                });&lt;br /&gt;
                var chunks = [];&lt;br /&gt;
&lt;br /&gt;
                while (pending.length) chunks.push(pending.splice(0, 20));&lt;br /&gt;
                if (!chunks.length) return flagUrlCache;&lt;br /&gt;
&lt;br /&gt;
                return Promise.all(chunks.map(function (chunk) {&lt;br /&gt;
                    var titleToKey = {};&lt;br /&gt;
                    var keyToFile = {};&lt;br /&gt;
                    var titles = [];&lt;br /&gt;
                    chunk.forEach(function (file) {&lt;br /&gt;
                        var key = fileKey(file);&lt;br /&gt;
                        if (!key) return;&lt;br /&gt;
                        titleToKey[fileKey(&#039;File:&#039; + file)] = key;&lt;br /&gt;
                        titleToKey[fileKey(&#039;파일:&#039; + file)] = key;&lt;br /&gt;
                        keyToFile[key] = file;&lt;br /&gt;
                        titles.push(&#039;File:&#039; + file);&lt;br /&gt;
                        titles.push(&#039;파일:&#039; + file);&lt;br /&gt;
                    });&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size&#039;,&lt;br /&gt;
                        iiurlwidth: &#039;16&#039;,&lt;br /&gt;
                        titles: titles.join(&#039;|&#039;)&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var seen = {};&lt;br /&gt;
                        pages.forEach(function (page) {&lt;br /&gt;
                            var key = titleToKey[fileKey(page &amp;amp;&amp;amp; page.title)] || fileKey(page &amp;amp;&amp;amp; page.title);&lt;br /&gt;
                            var file = keyToFile[key] || (page &amp;amp;&amp;amp; page.title || &#039;&#039;).replace(/^(?:File|파일):/i, &#039;&#039;);&lt;br /&gt;
                            var imageinfo = page &amp;amp;&amp;amp; page.imageinfo &amp;amp;&amp;amp; page.imageinfo[0];&lt;br /&gt;
                            var url;&lt;br /&gt;
                            var rev;&lt;br /&gt;
                            var sep;&lt;br /&gt;
                            var cacheKey;&lt;br /&gt;
                            if (!key) return;&lt;br /&gt;
                            seen[key] = true;&lt;br /&gt;
                            url = imageinfo &amp;amp;&amp;amp; (imageinfo.thumburl || imageinfo.url) ? (imageinfo.thumburl || imageinfo.url) : &#039;&#039;;&lt;br /&gt;
                            if (url) {&lt;br /&gt;
                                rev = imageinfo &amp;amp;&amp;amp; (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) ? (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) : &#039;&#039;;&lt;br /&gt;
                                if (rev &amp;amp;&amp;amp; !/[?&amp;amp;]_entryFileRev=/.test(url)) {&lt;br /&gt;
                                    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                                    url += sep + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                                }&lt;br /&gt;
                            }&lt;br /&gt;
                            flagUrlCache[key] = url;&lt;br /&gt;
                            cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.putText === &#039;function&#039;) {&lt;br /&gt;
                                window.EntryCache.putText(cacheKey, url || &#039;&#039;, {&lt;br /&gt;
                                    resourceKey: resourceKeyForFile(file),&lt;br /&gt;
                                    token: tokenForFile(file),&lt;br /&gt;
                                    kind: &#039;flagurl&#039;,&lt;br /&gt;
                                    contentType: &#039;text/plain; charset=UTF-8&#039;&lt;br /&gt;
                                });&lt;br /&gt;
                            }&lt;br /&gt;
                        });&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; !seen[key] &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () { return flagUrlCache; });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeGenericFileTitle(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            var match;&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            for (i = 0; i &amp;lt; 4; i += 1) {&lt;br /&gt;
                try {&lt;br /&gt;
                    if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;).replace(/^\/+/, &#039;&#039;).replace(/^index\.php\/?/i, &#039;&#039;).replace(/^wiki\/?/i, &#039;&#039;).trim();&lt;br /&gt;
            match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/(.+)$/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^(?:File|파일|Image|이미지)\s*:/i, &#039;&#039;).replace(/^:+/, &#039;&#039;).trim();&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isFileRef(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            return /^(?:file|파일)\s*:/i.test(text) || /(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\//i.test(text);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileUrlKey(value) {&lt;br /&gt;
            return normalizeGenericFileTitle(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function stableDirectImageUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var separator;&lt;br /&gt;
            var bust;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            if (/[?&amp;amp;]_entryAsset=/.test(text) || /[?&amp;amp;]_=/.test(text)) return text;&lt;br /&gt;
            bust = String(BUILD_ID || &#039;entry&#039;);&lt;br /&gt;
            separator = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + separator + &#039;_entryAsset=&#039; + encodeURIComponent(bust);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFileUrl(ref) {&lt;br /&gt;
            var original = String(ref || &#039;&#039;).trim();&lt;br /&gt;
            var file;&lt;br /&gt;
            var key;&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!original) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (!isFileRef(original)) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            file = normalizeGenericFileTitle(original);&lt;br /&gt;
            key = fileUrlKey(file);&lt;br /&gt;
            if (!file || !key) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            if (fileUrlCache[key]) return Promise.resolve(fileUrlCache[key]);&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;fileurl&#039;) : &#039;&#039;;&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + key);&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        fileUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return fileUrlCache[key];&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                        titles: &#039;File:&#039; + file&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                        var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                        var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                        var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                        var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                        fileUrlCache[key] = resolved;&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, resolved, { resourceKey: resourceKey, token: token, kind: &#039;fileurl&#039;, contentType: &#039;text/plain; charset=UTF-8&#039; });&lt;br /&gt;
                        return resolved;&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return stableDirectImageUrl(original);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
            return fetchApi({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;,&lt;br /&gt;
                redirects: &#039;1&#039;,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + file&lt;br /&gt;
            }).then(function (json) {&lt;br /&gt;
                var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                fileUrlCache[key] = resolved;&lt;br /&gt;
                return resolved;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                return stableDirectImageUrl(original);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImageUrl(url, options) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var sourcePromise;&lt;br /&gt;
            var persistent = !(options &amp;amp;&amp;amp; options.persistent === false);&lt;br /&gt;
            if (!key) return Promise.resolve(false);&lt;br /&gt;
            if (imageReadyCache[key]) return Promise.resolve(true);&lt;br /&gt;
            if (imagePromiseCache[key]) return imagePromiseCache[key];&lt;br /&gt;
&lt;br /&gt;
            cacheKey = persistent &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039; ? window.RevisionManifest.cacheKeyForUrl(key, &#039;image&#039;) : &#039;&#039;;&lt;br /&gt;
            sourcePromise = Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.fetchBlobUrl === &#039;function&#039;) {&lt;br /&gt;
                sourcePromise = window.EntryCache.fetchBlobUrl(key, cacheKey, {&lt;br /&gt;
                    resourceKey: &#039;&#039;,&lt;br /&gt;
                    token: &#039;&#039;,&lt;br /&gt;
                    kind: &#039;image&#039;&lt;br /&gt;
                }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            imagePromiseCache[key] = sourcePromise.then(function (cachedObjectUrl) {&lt;br /&gt;
                return new Promise(function (resolve) {&lt;br /&gt;
                    var img = new Image();&lt;br /&gt;
                    var settled = false;&lt;br /&gt;
                    var src = cachedObjectUrl || key;&lt;br /&gt;
                    function finish(ok) {&lt;br /&gt;
                        if (settled) return;&lt;br /&gt;
                        settled = true;&lt;br /&gt;
                        if (ok) {&lt;br /&gt;
                            imageReadyCache[key] = true;&lt;br /&gt;
                            imageReadyCache[src] = true;&lt;br /&gt;
                            imageObjectCache[key] = img;&lt;br /&gt;
                            imageObjectCache[src] = img;&lt;br /&gt;
                            imageDisplayUrlCache[key] = src;&lt;br /&gt;
                            imageDisplayUrlCache[src] = src;&lt;br /&gt;
                        }&lt;br /&gt;
                        resolve(!!ok);&lt;br /&gt;
                    }&lt;br /&gt;
                    img.onload = function () {&lt;br /&gt;
                        if (img.decode) {&lt;br /&gt;
                            img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        } else {&lt;br /&gt;
                            finish(true);&lt;br /&gt;
                        }&lt;br /&gt;
                    };&lt;br /&gt;
                    img.onerror = function () {&lt;br /&gt;
                        if (src !== key) {&lt;br /&gt;
                            src = key;&lt;br /&gt;
                            img.src = key;&lt;br /&gt;
                            return;&lt;br /&gt;
                        }&lt;br /&gt;
                        finish(false);&lt;br /&gt;
                    };&lt;br /&gt;
                    img.decoding = &#039;async&#039;;&lt;br /&gt;
                    img.loading = &#039;eager&#039;;&lt;br /&gt;
                    img.src = src;&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        if (img.decode) img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        else finish(true);&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
            return imagePromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImages(urls, options) {&lt;br /&gt;
            var list = unique((urls || []).filter(Boolean));&lt;br /&gt;
            var limit = options &amp;amp;&amp;amp; Number(options.limit);&lt;br /&gt;
            var concurrency = Math.max(1, Math.min(48, Number(options &amp;amp;&amp;amp; options.concurrency) || 24));&lt;br /&gt;
            var index = 0;&lt;br /&gt;
            var ok = 0;&lt;br /&gt;
            var fail = 0;&lt;br /&gt;
            if (Number.isFinite(limit) &amp;amp;&amp;amp; limit &amp;gt; 0) list = list.slice(0, limit);&lt;br /&gt;
            if (!list.length) return Promise.resolve({ total: 0, ok: 0, fail: 0 });&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                function pump() {&lt;br /&gt;
                    while (index &amp;lt; list.length &amp;amp;&amp;amp; concurrency &amp;gt; 0) {&lt;br /&gt;
                        (function (url) {&lt;br /&gt;
                            concurrency -= 1;&lt;br /&gt;
                            preloadImageUrl(url, options || {}).then(function (result) {&lt;br /&gt;
                                if (result) ok += 1;&lt;br /&gt;
                                else fail += 1;&lt;br /&gt;
                            }).catch(function () {&lt;br /&gt;
                                fail += 1;&lt;br /&gt;
                            }).then(function () {&lt;br /&gt;
                                concurrency += 1;&lt;br /&gt;
                                if (index &amp;gt;= list.length &amp;amp;&amp;amp; ok + fail &amp;gt;= list.length) resolve({ total: list.length, ok: ok, fail: fail });&lt;br /&gt;
                                else pump();&lt;br /&gt;
                            });&lt;br /&gt;
                        })(list[index++]);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                pump();&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isImageReady(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return !!(key &amp;amp;&amp;amp; imageReadyCache[key]);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageElement(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageObjectCache[key] || null) : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageDisplayUrl(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageDisplayUrlCache[key] || key) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeUrlKey(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var a;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                a = document.createElement(&#039;a&#039;);&lt;br /&gt;
                a.href = text;&lt;br /&gt;
                text = a.pathname + (a.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            try {&lt;br /&gt;
                text = decodeURI(text);&lt;br /&gt;
            } catch (err2) {}&lt;br /&gt;
            text = text.replace(/([?&amp;amp;])_=[^&amp;amp;]*/g, &#039;$1&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            text = text.replace(/_/g, &#039;_&#039;);&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchTextUrl(url, options) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceRef;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty text url&#039;));&lt;br /&gt;
            if (textCache[key]) return Promise.resolve(textCache[key].text);&lt;br /&gt;
            if (textPromiseCache[key]) return textPromiseCache[key];&lt;br /&gt;
            resourceRef = options &amp;amp;&amp;amp; options.resourceRef ? options.resourceRef : (options &amp;amp;&amp;amp; options.ref ? options.ref : &#039;&#039;);&lt;br /&gt;
            cacheKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(resourceRef, &#039;text&#039;) : &#039;&#039;;&lt;br /&gt;
            if (!cacheKey &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039;) cacheKey = window.RevisionManifest.cacheKeyForUrl(url, &#039;text&#039;);&lt;br /&gt;
            resourceKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(resourceRef) : (&#039;url:&#039; + key);&lt;br /&gt;
            token = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(resourceRef) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                textPromiseCache[key] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: cachedText, loadedAt: now(), persistent: true };&lt;br /&gt;
                        return cachedText;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token, kind: &#039;text&#039;, contentType: &#039;text/html; charset=UTF-8&#039; });&lt;br /&gt;
                        return text;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return textPromiseCache[key];&lt;br /&gt;
            }&lt;br /&gt;
            textPromiseCache[key] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                return text;&lt;br /&gt;
            });&lt;br /&gt;
            return textPromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getTextSync(url) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            return key &amp;amp;&amp;amp; textCache[key] ? textCache[key].text : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setTextUrl(url, text) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            textCache[key] = { key: key, url: url, text: String(text || &#039;&#039;), loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheInfo() {&lt;br /&gt;
            return {&lt;br /&gt;
                json: Object.keys(jsonCache).length,&lt;br /&gt;
                jsonKeys: Object.keys(jsonCache),&lt;br /&gt;
                flags: Object.keys(flagUrlCache).length,&lt;br /&gt;
                files: Object.keys(fileUrlCache).length,&lt;br /&gt;
                images: Object.keys(imageReadyCache).length,&lt;br /&gt;
                retainedImages: Object.keys(imageObjectCache).length,&lt;br /&gt;
                displayImages: Object.keys(imageDisplayUrlCache).length,&lt;br /&gt;
                text: Object.keys(textCache).length,&lt;br /&gt;
                textKeys: Object.keys(textCache)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            fetchJsonRef: fetchJsonRef,&lt;br /&gt;
            getJsonSync: getJsonSync,&lt;br /&gt;
            setJsonRef: setJsonRef,&lt;br /&gt;
            normalizeRefKey: normalizeRefKey,&lt;br /&gt;
            rawUrlForRef: rawUrlForRef,&lt;br /&gt;
            revisionUrlForRef: function (ref, ctype) {&lt;br /&gt;
                var url = rawUrlForRef(ref, ctype);&lt;br /&gt;
                if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                    url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
                }&lt;br /&gt;
                return url;&lt;br /&gt;
            },&lt;br /&gt;
            resolveFlagUrls: resolveFlagUrls,&lt;br /&gt;
            setFlagUrl: setFlagUrl,&lt;br /&gt;
            getFlagUrl: getFlagUrl,&lt;br /&gt;
            resolveFileUrl: resolveFileUrl,&lt;br /&gt;
            preloadImageUrl: preloadImageUrl,&lt;br /&gt;
            preloadImages: preloadImages,&lt;br /&gt;
            isImageReady: isImageReady,&lt;br /&gt;
            getImageElement: getImageElement,&lt;br /&gt;
            getImageDisplayUrl: getImageDisplayUrl,&lt;br /&gt;
            fetchTextUrl: fetchTextUrl,&lt;br /&gt;
            getTextSync: getTextSync,&lt;br /&gt;
            setTextUrl: setTextUrl,&lt;br /&gt;
            stableDirectImageUrl: stableDirectImageUrl,&lt;br /&gt;
            cacheInfo: cacheInfo&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryStore = window.EntryStore || createEntryStore();&lt;br /&gt;
&lt;br /&gt;
    function updateBootProgress(done, total, label) {&lt;br /&gt;
        var pct = total ? Math.max(0, Math.min(100, Math.round((done / total) * 100))) : 0;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootDetailNode &amp;amp;&amp;amp; label) bootDetailNode.textContent = label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function adoptBootScreen(node) {&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        bootNode = node;&lt;br /&gt;
        bootStatusNode = bootNode.querySelector(&#039;.boot-gate-status&#039;);&lt;br /&gt;
        bootProgressNode = bootNode.querySelector(&#039;.boot-gate-progress&#039;);&lt;br /&gt;
        bootDetailNode = bootNode.querySelector(&#039;.boot-gate-detail&#039;);&lt;br /&gt;
        bootFillNode = bootNode.querySelector(&#039;.boot-gate-meter-fill&#039;);&lt;br /&gt;
        bootNode.classList.add(&#039;is-active&#039;);&lt;br /&gt;
        bootNode.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBootScreen(options) {&lt;br /&gt;
        var panel;&lt;br /&gt;
        var header;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var existing;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
        var previewMode;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        previewMode = !!options.preview;&lt;br /&gt;
        if (bootNode &amp;amp;&amp;amp; bootNode.parentNode) return bootNode;&lt;br /&gt;
        if (!document.body) return null;&lt;br /&gt;
&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !previewMode) return null;&lt;br /&gt;
        if (!previewMode) activateBootSurface();&lt;br /&gt;
&lt;br /&gt;
        existing = document.getElementById(&#039;boot-gate-screen&#039;) || (!previewMode &amp;amp;&amp;amp; window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.ensure ? window.__BootGatePrelude.ensure() : null);&lt;br /&gt;
        if (existing) return adoptBootScreen(existing);&lt;br /&gt;
&lt;br /&gt;
        bootNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootNode.id = &#039;boot-gate-screen&#039;;&lt;br /&gt;
        bootNode.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        bootNode.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        bootNode.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        header = document.createElement(&#039;div&#039;);&lt;br /&gt;
        header.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        header.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootStatusNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootStatusNode.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        bootStatusNode.textContent = &#039;Preparing entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        bootFillNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootFillNode.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(bootFillNode);&lt;br /&gt;
&lt;br /&gt;
        bootProgressNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootProgressNode.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        bootProgressNode.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootDetailNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootDetailNode.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        bootDetailNode.textContent = &#039;loading manifest&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { hideBootScreen({ force: true }); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(header);&lt;br /&gt;
        panel.appendChild(bootStatusNode);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(bootProgressNode);&lt;br /&gt;
        panel.appendChild(bootDetailNode);&lt;br /&gt;
        bootNode.appendChild(decoLayer);&lt;br /&gt;
        bootNode.appendChild(panel);&lt;br /&gt;
        bootNode.appendChild(close);&lt;br /&gt;
        document.body.appendChild(bootNode);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildLoginUrl() {&lt;br /&gt;
        var pageName = String(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        var params = {};&lt;br /&gt;
&lt;br /&gt;
        if (pageName &amp;amp;&amp;amp; !isAuthenticationPage() &amp;amp;&amp;amp; !isCreateAccountPage()) params.returnto = pageName;&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                return mw.util.getUrl(&#039;Special:UserLogin&#039;, params);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return &#039;/index.php?title=Special%3AUserLogin&#039; + (params.returnto ? &#039;&amp;amp;returnto=&#039; + encodeURIComponent(params.returnto) : &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureLoginGateAction(panel) {&lt;br /&gt;
        var copy;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (loginGateActionNode &amp;amp;&amp;amp; loginGateActionNode.parentNode) return loginGateActionNode;&lt;br /&gt;
        loginGateActionNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        loginGateActionNode.className = &#039;boot-gate-login&#039;;&lt;br /&gt;
&lt;br /&gt;
        copy = document.createElement(&#039;div&#039;);&lt;br /&gt;
        copy.className = &#039;boot-gate-login-copy&#039;;&lt;br /&gt;
        copy.textContent = &#039;SIGN IN TO ENTER THE WIKI.&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = document.createElement(&#039;a&#039;);&lt;br /&gt;
        action.className = &#039;boot-gate-login-action&#039;;&lt;br /&gt;
        action.href = buildLoginUrl();&lt;br /&gt;
        action.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        action.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
&lt;br /&gt;
        loginGateActionNode.appendChild(copy);&lt;br /&gt;
        loginGateActionNode.appendChild(action);&lt;br /&gt;
        panel.appendChild(loginGateActionNode);&lt;br /&gt;
        return loginGateActionNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showLoginGate() {&lt;br /&gt;
        var node = ensureBootScreen();&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (!node || !requiresLoginGate()) return false;&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        loginGateLocked = true;&lt;br /&gt;
        node.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.add(&#039;is-active&#039;, &#039;is-login-required&#039;);&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;dialog&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-modal&#039;, &#039;true&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;off&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = node.querySelector(&#039;.boot-gate-panel&#039;);&lt;br /&gt;
        title = node.querySelector(&#039;.boot-gate-title&#039;);&lt;br /&gt;
        if (title) title.textContent = &#039;ACCOUNT AUTHENTICATION&#039;;&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = &#039;LOGIN REQUIRED&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = &#039;100%&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = panel ? ensureLoginGateAction(panel).querySelector(&#039;.boot-gate-login-action&#039;) : null;&lt;br /&gt;
        if (action) {&lt;br /&gt;
            action.href = buildLoginUrl();&lt;br /&gt;
            window.requestAnimationFrame(function () {&lt;br /&gt;
                try { action.focus({ preventScroll: true }); }&lt;br /&gt;
                catch (err) { try { action.focus(); } catch (ignore) {} }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hideBootScreen(options) {&lt;br /&gt;
        var node = bootNode || document.getElementById(&#039;boot-gate-screen&#039;);&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        if (loginGateLocked &amp;amp;&amp;amp; requiresLoginGate() &amp;amp;&amp;amp; !options.force) return false;&lt;br /&gt;
        if (!node) {&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            if (bootNode === node) bootNode = null;&lt;br /&gt;
            loginGateActionNode = null;&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 240);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromNationPayload(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            if (typeof value === &#039;string&#039;) {&lt;br /&gt;
                files.push(value);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof value === &#039;object&#039;) files.push(value.file || value.flag_file || value.flag || value.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function scanItem(item) {&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            if (Array.isArray(item.flags)) item.flags.forEach(add);&lt;br /&gt;
            add(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        (payload &amp;amp;&amp;amp; payload.continents || []).forEach(function (continent) {&lt;br /&gt;
            (continent.regions || []).forEach(function (region) {&lt;br /&gt;
                (region.items || []).forEach(scanItem);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromLinkMap(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        var source = payload &amp;amp;&amp;amp; payload.items ? payload.items : {};&lt;br /&gt;
        Object.keys(source || {}).forEach(function (key) {&lt;br /&gt;
            var item = source[key];&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            files.push(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prewarmImages(urls, limit) {&lt;br /&gt;
        return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
            limit: Number(limit) &amp;gt; 0 ? Number(limit) : 0,&lt;br /&gt;
            concurrency: 16,&lt;br /&gt;
            persistent: false&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationRuntime() {&lt;br /&gt;
        return window.Decorations || window.CLBI_DECORATIONS || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationRuntime() {&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var tries = 0;&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var runtime = getDecorationRuntime();&lt;br /&gt;
                if (runtime) return resolve(runtime);&lt;br /&gt;
                tries += 1;&lt;br /&gt;
                if (tries &amp;gt; 40) return resolve(null);&lt;br /&gt;
                window.setTimeout(tick, 25);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesDecorationEntry(entry, filter) {&lt;br /&gt;
        var era = String(filter &amp;amp;&amp;amp; filter.era || &#039;&#039;).trim();&lt;br /&gt;
        var page = String(filter &amp;amp;&amp;amp; filter.page || &#039;&#039;).trim();&lt;br /&gt;
        var entryPage = String(entry &amp;amp;&amp;amp; entry.page || &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039;) return false;&lt;br /&gt;
        if (page &amp;amp;&amp;amp; entryPage &amp;amp;&amp;amp; entryPage !== page) return false;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() !== era) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDecorationSet(task, level) {&lt;br /&gt;
        var ref = task.ref || &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
        return window.EntryStore.fetchJsonRef(ref, { noStore: !!task.noStore }).then(function (registry) {&lt;br /&gt;
            var list = registry &amp;amp;&amp;amp; Array.isArray(registry.decorations) ? registry.decorations : [];&lt;br /&gt;
            var pixelRefs = [];&lt;br /&gt;
            list.forEach(function (entry) {&lt;br /&gt;
                var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).toLowerCase();&lt;br /&gt;
                var asset = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
                if (!asset) return;&lt;br /&gt;
                if (type !== &#039;pixel-json&#039; &amp;amp;&amp;amp; !/\.json(?:[?#].*)?$/i.test(asset)) return;&lt;br /&gt;
                if (!matchesDecorationEntry(entry, task)) return;&lt;br /&gt;
                pixelRefs.push(asset);&lt;br /&gt;
            });&lt;br /&gt;
            if (level !== &#039;full&#039; || task.preparePixels === false || !pixelRefs.length) return registry;&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (!runtime || typeof runtime.preparePixelCanvas !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                        return window.EntryStore.fetchJsonRef(pixelRef).catch(function () { return null; });&lt;br /&gt;
                    })).then(function () { return registry; });&lt;br /&gt;
                }&lt;br /&gt;
                return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(pixelRef).catch(function () { return null; });&lt;br /&gt;
                })).then(function () { return registry; });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareNationsEra(task, level) {&lt;br /&gt;
        var era = String(task.era || &#039;1950&#039;);&lt;br /&gt;
        var listRef = task.listRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_List.json&#039;);&lt;br /&gt;
        var linkRef = task.linkMapRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_Link_Map.json&#039;);&lt;br /&gt;
        var jsonPhaseId = BootPerf.start(&#039;nations-era json fetch&#039;, { era: era, level: level, listRef: listRef, linkRef: linkRef });&lt;br /&gt;
        var listPromise = window.EntryStore.fetchJsonRef(listRef).catch(function () { return null; });&lt;br /&gt;
        var linkPromise = window.EntryStore.fetchJsonRef(linkRef).catch(function () { return null; });&lt;br /&gt;
&lt;br /&gt;
        return Promise.all([listPromise, linkPromise]).then(function (results) {&lt;br /&gt;
            BootPerf.end(jsonPhaseId, { ok: true });&lt;br /&gt;
            var files;&lt;br /&gt;
            var flagUrls;&lt;br /&gt;
            if (level !== &#039;full&#039; &amp;amp;&amp;amp; level !== &#039;warm&#039;) return results;&lt;br /&gt;
            files = unique(collectFlagsFromNationPayload(results[0]).concat(collectFlagsFromLinkMap(results[1])));&lt;br /&gt;
            return BootPerf.measure(&#039;nations-era flag url resolve&#039;, { era: era, count: files.length }, function () {&lt;br /&gt;
                return window.EntryStore.resolveFlagUrls(files);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                if (level === &#039;warm&#039;) {&lt;br /&gt;
                    /*&lt;br /&gt;
                     * Warm tab contract — 20260708.&lt;br /&gt;
                     *&lt;br /&gt;
                     * A previous tab already stored the same-revision flag URL and image&lt;br /&gt;
                     * blobs.  Re-decoding every flag here makes the second tab feel as slow&lt;br /&gt;
                     * as the first one.  In warm mode we only hydrate the flag URL table from&lt;br /&gt;
                     * EntryCache so NationsPanel can render stable URLs immediately; image&lt;br /&gt;
                     * decode remains demand-driven by the live panel instead of blocking the&lt;br /&gt;
                     * boot screen.&lt;br /&gt;
                     */&lt;br /&gt;
                    return results;&lt;br /&gt;
                }&lt;br /&gt;
                flagUrls = files.map(function (file) { return window.EntryStore.getFlagUrl(file); }).filter(Boolean);&lt;br /&gt;
                /*&lt;br /&gt;
                 * 20260710: Flag URLs are part of the entry contract, but decoding every&lt;br /&gt;
                 * flag image is not.  The 1950 list has more than 150 flags, and waiting&lt;br /&gt;
                 * for all image decodes kept the public boot gate open for several seconds.&lt;br /&gt;
                 * Keep revision-aware URLs ready for the panel, then let the browser load&lt;br /&gt;
                 * the actual images from the live DOM.  A low-priority background warm-up&lt;br /&gt;
                 * may run after the boot gate has opened, so it never competes with the&lt;br /&gt;
                 * current entry surface or the globe ready contract.&lt;br /&gt;
                 */&lt;br /&gt;
                BootPerf.mark(&#039;nations-era flag image prewarm skipped&#039;, {&lt;br /&gt;
                    era: era,&lt;br /&gt;
                    count: flagUrls.length,&lt;br /&gt;
                    limit: task.flagLimit == null ? 0 : Number(task.flagLimit),&lt;br /&gt;
                    reason: &#039;deferred-after-boot-gate&#039;&lt;br /&gt;
                });&lt;br /&gt;
                if (flagUrls.length &amp;amp;&amp;amp; task.deferFlagImages !== false) {&lt;br /&gt;
                    deferredEntryWarmups.push(function () {&lt;br /&gt;
                        return BootPerf.measure(&#039;deferred nations-era flag image prewarm&#039;, {&lt;br /&gt;
                            era: era,&lt;br /&gt;
                            count: flagUrls.length,&lt;br /&gt;
                            limit: task.flagLimit == null ? 0 : Number(task.flagLimit)&lt;br /&gt;
                        }, function () {&lt;br /&gt;
                            return prewarmImages(flagUrls, task.flagLimit == null ? 0 : Number(task.flagLimit));&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                return results;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareGlobeSharedAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;globe-shared url resolve&#039;, { count: refs.length, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            /*&lt;br /&gt;
             * Globe shared assets are heavy 6K texture sources.  The live&lt;br /&gt;
             * NationsGlobe ready contract already waits for the actual Three.js&lt;br /&gt;
             * texture path, so decoding the same images here only duplicates work&lt;br /&gt;
             * and holds the boot screen.  Keep revision-aware URL resolution in the&lt;br /&gt;
             * entry ledger, but make the image warm-up non-blocking.&lt;br /&gt;
             */&lt;br /&gt;
            BootPerf.mark(&#039;globe-shared image prewarm skipped&#039;, {&lt;br /&gt;
                count: urls.length,&lt;br /&gt;
                level: level,&lt;br /&gt;
                reason: &#039;deferred-to-nations-globe-ready-contract&#039;&lt;br /&gt;
            });&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (!window.EntryStore || typeof window.EntryStore.preloadImages !== &#039;function&#039;) return;&lt;br /&gt;
                window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 4)),&lt;br /&gt;
                    persistent: false&lt;br /&gt;
                }).catch(function () {});&lt;br /&gt;
            }, 0);&lt;br /&gt;
            return urls;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareImageAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        var full = level === &#039;full&#039;;&lt;br /&gt;
        var exposeAs = String(task &amp;amp;&amp;amp; task.exposeAs || &#039;&#039;).trim();&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;image-assets url resolve&#039;, { count: refs.length, exposeAs: exposeAs, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            if (exposeAs === &#039;nationsGlobeLoadingGif&#039;) {&lt;br /&gt;
                window.NationsGlobeLoadingGifRef = refs[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifUrl = urls[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifFile = &#039;Gfx-vhs-glitch-001.gif&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (!full) return urls;&lt;br /&gt;
            return BootPerf.measure(&#039;image-assets image prewarm&#039;, { count: urls.length, exposeAs: exposeAs, limit: task.imageLimit == null ? 0 : Number(task.imageLimit), concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2)) }, function () {&lt;br /&gt;
                return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2))&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () { return urls; });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareHtmlEntry(task) {&lt;br /&gt;
        var url = String(task &amp;amp;&amp;amp; (task.url || task.ref) || &#039;&#039;).trim();&lt;br /&gt;
        if (!url) return Promise.resolve(null);&lt;br /&gt;
        return window.EntryStore.fetchTextUrl(url, { noStore: !!task.noStore, resourceRef: task.resourceRef || task.ref || task.title || &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareTask(task, defaultLevel) {&lt;br /&gt;
        var level = String(task &amp;amp;&amp;amp; (task.level || defaultLevel) || &#039;half&#039;).toLowerCase();&lt;br /&gt;
        var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
        if (!task || typeof task !== &#039;object&#039;) return Promise.resolve(null);&lt;br /&gt;
        if (type === &#039;html&#039;) return prepareHtmlEntry(task);&lt;br /&gt;
        if (type === &#039;json&#039;) return window.EntryStore.fetchJsonRef(task.ref, { noStore: !!task.noStore });&lt;br /&gt;
        if (type === &#039;pixel-json&#039;) {&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (level === &#039;full&#039; &amp;amp;&amp;amp; runtime &amp;amp;&amp;amp; typeof runtime.preparePixelCanvas === &#039;function&#039;) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(task.ref || task.asset);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(task.ref || task.asset);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        if (type === &#039;decorations&#039;) return prepareDecorationSet(task, level);&lt;br /&gt;
        if (type === &#039;nations-era&#039;) return prepareNationsEra(task, level);&lt;br /&gt;
        if (type === &#039;globe-shared-assets&#039;) return prepareGlobeSharedAssets(task, level);&lt;br /&gt;
        if (type === &#039;image-assets&#039;) return prepareImageAssets(task, level);&lt;br /&gt;
        return Promise.resolve(null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifest() {&lt;br /&gt;
        return BootPerf.measure(&#039;revision manifest load&#039;, {}, function () {&lt;br /&gt;
            return (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.load === &#039;function&#039; ? window.RevisionManifest.load() : Promise.resolve(null));&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;entry manifest json fetch&#039;, { ref: MANIFEST_TITLE }, function () {&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(MANIFEST_TITLE, { noStore: false });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (manifest) {&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039; || !manifest.version) {&lt;br /&gt;
                BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: &#039;invalid manifest&#039; });&lt;br /&gt;
                return defaultManifest;&lt;br /&gt;
            }&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest ready&#039;, { version: manifest.version });&lt;br /&gt;
            return manifest;&lt;br /&gt;
        }).catch(function (err) {&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: err &amp;amp;&amp;amp; (err.message || String(err)) || &#039;load failed&#039; });&lt;br /&gt;
            return defaultManifest;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function flattenInitialTasks(manifest) {&lt;br /&gt;
        var initial = manifest &amp;amp;&amp;amp; manifest.initial ? manifest.initial : {};&lt;br /&gt;
        var full = Array.isArray(initial.full) ? initial.full : [];&lt;br /&gt;
        var half = Array.isArray(initial.half) ? initial.half : [];&lt;br /&gt;
        var tasks = [];&lt;br /&gt;
        full.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;full&#039;;&lt;br /&gt;
            task.blocking = true;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        half.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;half&#039;;&lt;br /&gt;
            task.blocking = false;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        return tasks;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function flattenWarmInitialTasks(tasks) {&lt;br /&gt;
        var warmed = [];&lt;br /&gt;
        (tasks || []).forEach(function (task) {&lt;br /&gt;
            var copy;&lt;br /&gt;
            var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
            if (!task || task.blocking === false) return;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Warm boot fast path — 20260708.&lt;br /&gt;
             *&lt;br /&gt;
             * Cold boot intentionally performs the expensive work: resolving every flag,&lt;br /&gt;
             * downloading/decoding images, preparing decoration canvases, and warming shared&lt;br /&gt;
             * globe textures.  Once a tab has certified the pack against the current revision&lt;br /&gt;
             * manifest, later tabs must not replay that full workload.  They only hydrate the&lt;br /&gt;
             * small tables needed by the live page and let already-cached image/blob data be&lt;br /&gt;
             * consumed on demand.  This is the missing layer that made second tabs feel almost&lt;br /&gt;
             * as slow as first tabs even though the string checks were true.&lt;br /&gt;
             */&lt;br /&gt;
            if (type === &#039;globe-shared-assets&#039;) return;&lt;br /&gt;
            if (type === &#039;image-assets&#039;) return;&lt;br /&gt;
            copy = Object.assign({}, task);&lt;br /&gt;
            if (type === &#039;nations-era&#039;) copy.level = &#039;warm&#039;;&lt;br /&gt;
            else if (type === &#039;decorations&#039;) {&lt;br /&gt;
                copy.level = &#039;half&#039;;&lt;br /&gt;
                copy.preparePixels = false;&lt;br /&gt;
            } else if (type === &#039;pixel-json&#039;) copy.level = &#039;half&#039;;&lt;br /&gt;
            warmed.push(copy);&lt;br /&gt;
        });&lt;br /&gt;
        return warmed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackKey(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        return &#039;initial-entry-full:&#039; + version;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackToken(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        var revToken = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(version) : version;&lt;br /&gt;
        return revToken;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isInitialPackWarm(manifest) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        return !!(window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.packReady === &#039;function&#039; &amp;amp;&amp;amp; window.EntryCache.packReady(key, token));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function markInitialPackWarm(manifest, meta) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        if (window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.setPackReady === &#039;function&#039;) {&lt;br /&gt;
            window.EntryCache.setPackReady(key, token, Object.assign({ manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || BUILD_ID }, meta || {}));&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function waitMs(ms) {&lt;br /&gt;
        return new Promise(function (resolve) { window.setTimeout(resolve, Math.max(0, ms || 0)); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForBody() {&lt;br /&gt;
        if (document.body) return Promise.resolve(document.body);&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                if (document.body) return resolve(document.body);&lt;br /&gt;
                window.setTimeout(tick, 10);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDomReady() {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) return Promise.resolve();&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, resolve, { once: true });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForAnimationFrames(count) {&lt;br /&gt;
        count = Math.max(1, Math.round(count || 1));&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function next(left) {&lt;br /&gt;
                if (left &amp;lt;= 0) return resolve();&lt;br /&gt;
                window.requestAnimationFrame(function () { next(left - 1); });&lt;br /&gt;
            }&lt;br /&gt;
            next(count);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitUntil(predicate, options) {&lt;br /&gt;
        var started = now();&lt;br /&gt;
        var timeout = options &amp;amp;&amp;amp; options.timeoutMs ? options.timeoutMs : 8000;&lt;br /&gt;
        var interval = options &amp;amp;&amp;amp; options.intervalMs ? options.intervalMs : 60;&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var result = false;&lt;br /&gt;
                try { result = predicate(); } catch (err) { result = false; }&lt;br /&gt;
                if (result) return resolve({ ok: true, value: result });&lt;br /&gt;
                if (now() - started &amp;gt;= timeout) return resolve({ ok: false, timeout: true });&lt;br /&gt;
                window.setTimeout(tick, interval);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForTrackedScripts() {&lt;br /&gt;
        var list = (window.EntryScriptLoads || []).slice();&lt;br /&gt;
        if (!list.length) return Promise.resolve([]);&lt;br /&gt;
        return Promise.all(list.map(function (promise) {&lt;br /&gt;
            return Promise.resolve(promise).catch(function (err) { return { ok: false, error: err }; });&lt;br /&gt;
        }));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDocumentSurface() {&lt;br /&gt;
        return waitForDomReady().then(function () {&lt;br /&gt;
            return waitForBody();&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                return document.querySelector(&#039;.content-wrapper&#039;) &amp;amp;&amp;amp; document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
            }, { timeoutMs: 8000, intervalMs: 50 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isNationsPageSurface() {&lt;br /&gt;
        var page = String(mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
        return !!document.querySelector(&#039;.clbi-nations-panel-stack&#039;) || /(?:^|[_ ])시대(?:$|[_ ])/.test(page) || /(?:^|[_ ])Era(?:$|[_ ])/i.test(page);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getInitialNationsEra(manifest) {&lt;br /&gt;
        var tasks = flattenInitialTasks(manifest);&lt;br /&gt;
        var i;&lt;br /&gt;
        for (i = 0; i &amp;lt; tasks.length; i += 1) {&lt;br /&gt;
            if (tasks[i] &amp;amp;&amp;amp; tasks[i].type === &#039;nations-era&#039; &amp;amp;&amp;amp; String(tasks[i].level || &#039;&#039;).toLowerCase() === &#039;full&#039;) {&lt;br /&gt;
                return String(tasks[i].era || &#039;1950&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;1950&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsPanelReady(era) {&lt;br /&gt;
        return BootPerf.measure(&#039;wait nations panel api&#039;, { era: era }, function () {&lt;br /&gt;
            return waitUntil(function () { return window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.whenEraReady === &#039;function&#039;; }, {&lt;br /&gt;
                timeoutMs: 8000,&lt;br /&gt;
                intervalMs: 50&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (result) {&lt;br /&gt;
            if (!result.ok || !window.NationsPanel || typeof window.NationsPanel.whenEraReady !== &#039;function&#039;) return null;&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel era ready&#039;, { era: era }, function () {&lt;br /&gt;
                return window.NationsPanel.whenEraReady(era, { timeoutMs: 15000 }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel dom ready&#039;, { era: era }, function () {&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var panel = document.querySelector(&#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + era + &#039;&amp;quot;] .clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;) ||&lt;br /&gt;
                        document.querySelector(&#039;.clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
                    return panel &amp;amp;&amp;amp; panel.classList.contains(&#039;clbi-nations-list-json-ready&#039;) &amp;amp;&amp;amp; panel.getAttribute(&#039;data-nation-list-year-loaded&#039;) === String(era);&lt;br /&gt;
                }, { timeoutMs: 15000, intervalMs: 80 });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsGlobeReady(options) {&lt;br /&gt;
        var globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
        var soft;&lt;br /&gt;
        var timeout;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        soft = !!options.soft;&lt;br /&gt;
        timeout = Number(options.timeoutMs || (soft ? 450 : 30000));&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            BootPerf.mark(&#039;wait nations globe skipped&#039;, { reason: &#039;no globe node&#039;, soft: soft });&lt;br /&gt;
            return Promise.resolve(null);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return BootPerf.measure(soft ? &#039;wait nations globe warm attach&#039; : &#039;wait nations globe ready contract&#039;, { soft: soft, timeoutMs: timeout }, function () {&lt;br /&gt;
            if (soft) {&lt;br /&gt;
                /*&lt;br /&gt;
                Warm-tab policy:&lt;br /&gt;
                Raw globe images are already revision-checked and blob-cached by EntryCache,&lt;br /&gt;
                but WebGL scene creation, GPU upload, topojson parsing, and per-tab Three.js&lt;br /&gt;
                objects cannot be shared across browser tabs.  Waiting for the full is-ready&lt;br /&gt;
                contract here makes a warm tab slower than the first tab.  In warm mode the&lt;br /&gt;
                initial gate only waits until the globe consumer has been attached, then the&lt;br /&gt;
                globe finishes its own per-tab hydrate without holding the whole page hostage.&lt;br /&gt;
                */&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                    if (!node) return false;&lt;br /&gt;
                    return node.getAttribute(&#039;data-nations-globe-ready&#039;) === &#039;1&#039; ||&lt;br /&gt;
                        !!node.CLBI_NationsGlobeInstance ||&lt;br /&gt;
                        !!node.querySelector(&#039;.clbi-nations-globe-stage&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;is-ready&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;has-error&#039;);&lt;br /&gt;
                }, { timeoutMs: timeout, intervalMs: 40 });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                return node &amp;amp;&amp;amp; (node.classList.contains(&#039;is-ready&#039;) || node.classList.contains(&#039;has-error&#039;));&lt;br /&gt;
            }, { timeoutMs: timeout, intervalMs: 100 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationsReady(options) {&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
            if (!runtime) return null;&lt;br /&gt;
            if (options.warm) {&lt;br /&gt;
                /*&lt;br /&gt;
                 * Warm tab contract — 20260708.&lt;br /&gt;
                 *&lt;br /&gt;
                 * The decoration registry and pixel canvases were prepared by the cold tab.&lt;br /&gt;
                 * Calling reload() here re-reads and re-syncs the decoration layer during the&lt;br /&gt;
                 * boot gate, which is visible as a pointless delay.  A warm tab only needs the&lt;br /&gt;
                 * current visibility pass so boot can release quickly and editing/preview tools&lt;br /&gt;
                 * remain responsive.&lt;br /&gt;
                 */&lt;br /&gt;
                try {&lt;br /&gt;
                    if (typeof runtime.updateVisibility === &#039;function&#039;) runtime.updateVisibility(document);&lt;br /&gt;
                    else if (typeof runtime.sync === &#039;function&#039;) runtime.sync();&lt;br /&gt;
                } catch (err) {}&lt;br /&gt;
                return waitForAnimationFrames(1);&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof runtime.reload !== &#039;function&#039;) return null;&lt;br /&gt;
            return runtime.reload().catch(function () { return null; }).then(function () {&lt;br /&gt;
                return waitForAnimationFrames(2);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForCurrentEntrySurface(manifest, options) {&lt;br /&gt;
        var era = getInitialNationsEra(manifest);&lt;br /&gt;
        var warmPack;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        warmPack = !!options.warmPack;&lt;br /&gt;
&lt;br /&gt;
        if (!isNationsPageSurface()) {&lt;br /&gt;
            return waitForDecorationsReady({ warm: !!warmPack }).then(function () { return waitForAnimationFrames(1); });&lt;br /&gt;
        }&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = warmPack ? &#039;Hydrating current information surface from warm cache&#039; : &#039;Preparing current information surface&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for era &#039; + era + &#039; ready contract&#039;;&lt;br /&gt;
        return waitForNationsPanelReady(era)&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (warmPack) {&lt;br /&gt;
                    if (bootDetailNode) bootDetailNode.textContent = &#039;attaching globe warm consumer&#039;;&lt;br /&gt;
                    return waitForNationsGlobeReady({ soft: true, timeoutMs: 450 });&lt;br /&gt;
                }&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for globe ready contract&#039;;&lt;br /&gt;
                return waitForNationsGlobeReady();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for decoration surface&#039;;&lt;br /&gt;
                return waitForDecorationsReady({ warm: warmPack });&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () { return waitForAnimationFrames(warmPack ? 1 : 2); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runDeferredEntryWarmups() {&lt;br /&gt;
        var queue = deferredEntryWarmups.splice(0);&lt;br /&gt;
        if (!queue.length) return;&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            var chain = Promise.resolve();&lt;br /&gt;
            queue.forEach(function (job) {&lt;br /&gt;
                chain = chain.then(function () {&lt;br /&gt;
                    try { return job(); }&lt;br /&gt;
                    catch (err) { return null; }&lt;br /&gt;
                }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }, 1200);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runInitialLoad(options) {&lt;br /&gt;
        var done = 0;&lt;br /&gt;
        var manifestRef;&lt;br /&gt;
        var tasks;&lt;br /&gt;
        var bootOptions;&lt;br /&gt;
        var timedOut = false;&lt;br /&gt;
        var warmPack = false;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        bootStartTime = window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.startTime ? window.__BootGatePrelude.startTime : now();&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        ensureBootScreen();&lt;br /&gt;
        updateBootProgress(0, 1, &#039;loading entry manifest&#039;);&lt;br /&gt;
&lt;br /&gt;
        manifestRef = loadManifest().then(function (manifest) {&lt;br /&gt;
            var minDisplay;&lt;br /&gt;
            var totalSteps;&lt;br /&gt;
            var maxBlockingMs;&lt;br /&gt;
            var timeoutHandle;&lt;br /&gt;
            bootOptions = manifest.boot || {};&lt;br /&gt;
            warmPack = isInitialPackWarm(manifest);&lt;br /&gt;
            minDisplay = options.minDisplayMs || (warmPack ? (bootOptions.cachedMinDisplayMs || 220) : (bootOptions.minDisplayMs || 950));&lt;br /&gt;
            tasks = flattenInitialTasks(manifest);&lt;br /&gt;
            if (!tasks.length) tasks = flattenInitialTasks(defaultManifest);&lt;br /&gt;
            if (warmPack) tasks = flattenWarmInitialTasks(tasks);&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            Full/half contract:&lt;br /&gt;
            These manifest tasks only prepare data assets.  The gate is not allowed to open&lt;br /&gt;
            until the current page surface has consumed that data and reported a real ready&lt;br /&gt;
            state below.  Do not reintroduce localStorage-only skip logic here; a cached&lt;br /&gt;
            version number is not the same as current-tab readiness.&lt;br /&gt;
            */&lt;br /&gt;
            totalSteps = tasks.length + 4;&lt;br /&gt;
            updateBootProgress(0, totalSteps, warmPack ? &#039;hydrating warm entry cache&#039; : &#039;starting entry packs&#039;);&lt;br /&gt;
&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                maxBlockingMs = Number(options.maxBlockingMs || bootOptions.maxBlockingMs || 30000);&lt;br /&gt;
                timeoutHandle = window.setTimeout(function () {&lt;br /&gt;
                    timedOut = true;&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }, maxBlockingMs);&lt;br /&gt;
&lt;br /&gt;
                Promise.all(tasks.map(function (task) {&lt;br /&gt;
                    var label = task.label || task.id || task.type || &#039;entry task&#039;;&lt;br /&gt;
                    return BootPerf.measure(&#039;boot task: &#039; + label, { id: task.id || &#039;&#039;, type: task.type || &#039;&#039;, level: task.level || task.level === 0 ? task.level : &#039;&#039; }, function () {&lt;br /&gt;
                        return prepareTask(task, task.level);&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }).then(function () {&lt;br /&gt;
                        done += 1;&lt;br /&gt;
                        updateBootProgress(done, totalSteps, label);&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;loading subsystem scripts&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait tracked subsystem scripts&#039;, { count: (window.EntryScriptLoads || []).length }, function () { return waitForTrackedScripts(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for document shell&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait document shell&#039;, {}, function () { return waitForDocumentSurface(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for current entry surface&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait current entry surface&#039;, { warmPack: warmPack }, function () { return waitForCurrentEntrySurface(manifest, { warmPack: warmPack }); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(totalSteps, totalSteps, warmPack ? &#039;warm entry surface complete&#039; : &#039;entry surface complete&#039;);&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                var elapsed = now() - bootStartTime;&lt;br /&gt;
                var wait = Math.max(0, minDisplay - elapsed);&lt;br /&gt;
                return waitMs(wait);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                localStorage.setItem(READY_KEY, String(manifest.version));&lt;br /&gt;
                if (!timedOut) markInitialPackWarm(manifest, { warmSource: warmPack ? &#039;cache-hit&#039; : &#039;cold-build&#039; });&lt;br /&gt;
                if (bootStatusNode) bootStatusNode.textContent = timedOut ? &#039;Entry surface ready with deferred items&#039; : (warmPack ? &#039;Entry surface ready from warm cache&#039; : &#039;Entry surface ready&#039;);&lt;br /&gt;
                updateBootProgress(tasks.length + 4, tasks.length + 4, timedOut ? &#039;timeout: continuing deferred loading&#039; : (warmPack ? &#039;warm cache complete&#039; : &#039;complete&#039;));&lt;br /&gt;
                BootPerf.mark(&#039;boot gate complete&#039;, { timedOut: timedOut, warmPack: warmPack, manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || &#039;&#039; });&lt;br /&gt;
                BootPerf.print();&lt;br /&gt;
                runDeferredEntryWarmups();&lt;br /&gt;
                window.setTimeout(function () {&lt;br /&gt;
                    if (requiresLoginGate()) showLoginGate();&lt;br /&gt;
                    else hideBootScreen();&lt;br /&gt;
                }, 120);&lt;br /&gt;
                return manifest;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return manifestRef;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startBoot(options) {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return Promise.resolve({ skipped: true, reason: &#039;developer-or-editing-page&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        if (hasBootParam(&#039;0&#039;) &amp;amp;&amp;amp; !requiresLoginGate() &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return Promise.resolve(null);&lt;br /&gt;
        if (bootStarted &amp;amp;&amp;amp; bootPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return bootPromise;&lt;br /&gt;
        bootStarted = true;&lt;br /&gt;
        bootPromise = runInitialLoad(options || {});&lt;br /&gt;
        return bootPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resetBoot() {&lt;br /&gt;
        localStorage.removeItem(READY_KEY);&lt;br /&gt;
        bootStarted = false;&lt;br /&gt;
        bootPromise = null;&lt;br /&gt;
        loginGateLocked = false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    BootGate intentionally has no SPA hold/release API.&lt;br /&gt;
    Initial boot is the only blocking phase; later SPA routes must consume prepared&lt;br /&gt;
    EntryStore artifacts without reopening the loading surface.&lt;br /&gt;
    */&lt;br /&gt;
&lt;br /&gt;
    function buildBootReport() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD_ID,&lt;br /&gt;
            state: {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                excludedPage: BOOT_EXCLUDED_PAGE,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;))&lt;br /&gt;
            },&lt;br /&gt;
            store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
            bootPerf: window.BootPerf &amp;amp;&amp;amp; typeof window.BootPerf.summary === &#039;function&#039; ? window.BootPerf.summary() : null,&lt;br /&gt;
            revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null,&lt;br /&gt;
            nations: window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.diagnostics === &#039;function&#039; ? window.NationsPanel.diagnostics() : null,&lt;br /&gt;
            decorations: window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.diagnostics === &#039;function&#039; ? window.Decorations.diagnostics() : null&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryLoader = window.EntryLoader || {&lt;br /&gt;
        loadManifest: loadManifest,&lt;br /&gt;
        prepareTask: prepareTask,&lt;br /&gt;
        prepareNationsEra: prepareNationsEra,&lt;br /&gt;
        prepareDecorationSet: prepareDecorationSet,&lt;br /&gt;
        prepareGlobeSharedAssets: prepareGlobeSharedAssets,&lt;br /&gt;
        prepareImageAssets: prepareImageAssets,&lt;br /&gt;
        prepareHtmlEntry: prepareHtmlEntry,&lt;br /&gt;
        runInitialLoad: runInitialLoad&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function showBootPreview(options) {&lt;br /&gt;
        var node;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        /*&lt;br /&gt;
         * Boot preview is a design/editing surface, not a real boot gate.&lt;br /&gt;
         * The earlier preview reused activateBootSurface(), which applied&lt;br /&gt;
         * html.boot-gate-active and hid the entire wiki shell, including&lt;br /&gt;
         * DevTools.  That made it impossible to edit loading-screen&lt;br /&gt;
         * decorations while previewing them.  Keep the real first-entry gate&lt;br /&gt;
         * full-screen, but make preview a small non-blocking surface below the&lt;br /&gt;
         * DevTools z-index so the owner can keep using the editor.&lt;br /&gt;
         */&lt;br /&gt;
        node = ensureBootScreen({ preview: true });&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        node.classList.add(&#039;is-preview&#039;);&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = options.status || &#039;Loading screen preview&#039;;&lt;br /&gt;
        updateBootProgress(Number(options.progress || 64), 100, options.detail || &#039;preview mode: no entry tasks are running&#039;);&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BootGate = window.BootGate || {&lt;br /&gt;
        version: BUILD_ID,&lt;br /&gt;
        start: startBoot,&lt;br /&gt;
        show: function (options) {&lt;br /&gt;
            options = options || {};&lt;br /&gt;
            options.force = true;&lt;br /&gt;
            resetBoot();&lt;br /&gt;
            return startBoot(options);&lt;br /&gt;
        },&lt;br /&gt;
        reset: resetBoot,&lt;br /&gt;
        hide: hideBootScreen,&lt;br /&gt;
        preview: showBootPreview,&lt;br /&gt;
        report: buildBootReport,&lt;br /&gt;
        state: function () {&lt;br /&gt;
            return {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                prelude: !!window.__BootGatePrelude,&lt;br /&gt;
                store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
                revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function prime() {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        waitForBody().then(function () {&lt;br /&gt;
            ensureBootScreen();&lt;br /&gt;
            return waitForAnimationFrames(1);&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            startBoot();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    waitForBody().then(function () {&lt;br /&gt;
        if (!BOOT_EXCLUDED_PAGE) ensureBootScreen();&lt;br /&gt;
        else hideBootScreen();&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(prime, 0);&lt;br /&gt;
})(window, document, window.mediaWiki || window.mw);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:DevTools.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsPanel.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsGlobe.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* CLBI safety guard: adaptive reset functions must exist before shell metric callbacks run. */&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.resetLeftRecentAdaptiveState = resetLeftRecentAdaptiveState;&lt;br /&gt;
window.resetLeftBillboardAdaptiveState = resetLeftBillboardAdaptiveState;&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:AnecdoteViewer.js&#039;);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SYSTEM_TITLE_NAMESPACES = {&lt;br /&gt;
        &#039;-1&#039;: true,&lt;br /&gt;
        &#039;4&#039;: true,&lt;br /&gt;
        &#039;5&#039;: true,&lt;br /&gt;
        &#039;6&#039;: true,&lt;br /&gt;
        &#039;7&#039;: true,&lt;br /&gt;
        &#039;8&#039;: true,&lt;br /&gt;
        &#039;9&#039;: true,&lt;br /&gt;
        &#039;10&#039;: true,&lt;br /&gt;
        &#039;11&#039;: true,&lt;br /&gt;
        &#039;12&#039;: true,&lt;br /&gt;
        &#039;13&#039;: true,&lt;br /&gt;
        &#039;14&#039;: true,&lt;br /&gt;
        &#039;15&#039;: true,&lt;br /&gt;
        &#039;828&#039;: true,&lt;br /&gt;
        &#039;829&#039;: true&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function normalizePageNameForShell(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readCurrentPageNameForShell() {&lt;br /&gt;
        var pageName = mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (pageName) {&lt;br /&gt;
            return normalizePageNameForShell(pageName);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return normalizePageNameForShell(window.location.pathname || &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnecdoteNamespaceForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var canonicalNamespace = String(mw.config.get(&#039;wgCanonicalNamespace&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 3000 ||&lt;br /&gt;
            canonicalNamespace === &#039;anecdote&#039; ||&lt;br /&gt;
            /^(anecdote|에넥도트):/i.test(pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBackendOrSystemPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var action = String(mw.config.get(&#039;wgAction&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var lowerPageName = pageName.toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === &#039;대문&#039;) {&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (SYSTEM_TITLE_NAMESPACES[String(namespaceNumber)]) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;json&#039; || contentModel === &#039;sanitized-css&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/\.(css|js|json)$/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/^(mediawiki|미디어위키|special|특수):/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMediaWikiSystemAssetPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 8 &amp;amp;&amp;amp;&lt;br /&gt;
            (/\.(css|js)$/i.test(pageName) || contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;sanitized-css&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var systemDocRawFetchToken = 0;&lt;br /&gt;
&lt;br /&gt;
    function cleanupLegacySystemDocCodeMutationsForShell() {&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codepane&#039;).forEach(function (pane) {&lt;br /&gt;
            var parent;&lt;br /&gt;
&lt;br /&gt;
            if (!pane || !pane.parentNode) return;&lt;br /&gt;
&lt;br /&gt;
            parent = pane.parentNode;&lt;br /&gt;
            while (pane.firstChild) {&lt;br /&gt;
                parent.insertBefore(pane.firstChild, pane);&lt;br /&gt;
            }&lt;br /&gt;
            parent.removeChild(pane);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codebox&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;style&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocOutputForShell() {&lt;br /&gt;
        return document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findSystemDocSourceNodeForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var children;&lt;br /&gt;
        var preferred;&lt;br /&gt;
&lt;br /&gt;
        if (!output) return null;&lt;br /&gt;
&lt;br /&gt;
        children = Array.prototype.slice.call(output.children || [])&lt;br /&gt;
            .filter(function (el) {&lt;br /&gt;
                return el &amp;amp;&amp;amp; el.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-doc-indicator-row&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-source-viewer&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    !el.classList.contains(&#039;catlinks&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    (el.textContent || &#039;&#039;).trim().length &amp;gt; 200;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
        preferred = children.filter(function (el) {&lt;br /&gt;
            return el.matches &amp;amp;&amp;amp; el.matches(&#039;.mw-highlight, .mw-code, pre&#039;);&lt;br /&gt;
        })[0];&lt;br /&gt;
&lt;br /&gt;
        return preferred || children.sort(function (a, b) {&lt;br /&gt;
            return (b.textContent || &#039;&#039;).trim().length - (a.textContent || &#039;&#039;).trim().length;&lt;br /&gt;
        })[0] || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocRawUrlForShell() {&lt;br /&gt;
        var title = mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell();&lt;br /&gt;
        var url;&lt;br /&gt;
&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            url = mw.util.getUrl(title, {&lt;br /&gt;
                action: &#039;raw&#039;,&lt;br /&gt;
                ctype: &#039;text/plain&#039;&lt;br /&gt;
            });&lt;br /&gt;
        } else {&lt;br /&gt;
            url = &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/plain&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, title);&lt;br /&gt;
        }&lt;br /&gt;
        return url;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (viewer &amp;amp;&amp;amp; viewer.parentNode) {&lt;br /&gt;
            viewer.parentNode.removeChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-original-source-hidden&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-source-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureSystemDocSourceViewerForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var source;&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var fallbackText;&lt;br /&gt;
&lt;br /&gt;
        if (!output || !isMediaWikiSystemAssetPageForShell()) return null;&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
&lt;br /&gt;
        source = findSystemDocSourceNodeForShell();&lt;br /&gt;
        if (!source) return null;&lt;br /&gt;
&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!viewer) {&lt;br /&gt;
            viewer = document.createElement(&#039;pre&#039;);&lt;br /&gt;
            viewer.id = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            viewer.className = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            output.appendChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fallbackText = source.textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!viewer.textContent &amp;amp;&amp;amp; fallbackText) {&lt;br /&gt;
            viewer.textContent = fallbackText;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        source.classList.add(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
        source.setAttribute(&#039;data-clbi-system-source-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        source.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
        return viewer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var token;&lt;br /&gt;
        var currentScrollTop;&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = String(mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell());&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        시스템 문서 뷰어가 이미 만들어져 있고 raw 원문도 로드된 상태라면&lt;br /&gt;
        다시 source 탐색/숨김/스타일 재적용을 하지 않는다.&lt;br /&gt;
        DevTools Elements 패널에서 body가 계속 파랗게 깜빡이던 원인은&lt;br /&gt;
        MutationObserver가 이 재적용을 반복해서 DOM attribute mutation을 만들었기 때문이다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            viewer &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-title&#039;) === pageName &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-loaded&#039;) === &#039;1&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        viewer = ensureSystemDocSourceViewerForShell();&lt;br /&gt;
        if (!viewer) return;&lt;br /&gt;
&lt;br /&gt;
        currentScrollTop = viewer.scrollTop || 0;&lt;br /&gt;
        viewer.setAttribute(&#039;data-clbi-raw-title&#039;, pageName);&lt;br /&gt;
        token = ++systemDocRawFetchToken;&lt;br /&gt;
&lt;br /&gt;
        fetch(getSystemDocRawUrlForShell(), { credentials: &#039;same-origin&#039; })&lt;br /&gt;
            .then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;raw fetch failed &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (token !== systemDocRawFetchToken) return;&lt;br /&gt;
&lt;br /&gt;
                currentScrollTop = viewer.scrollTop || currentScrollTop || 0;&lt;br /&gt;
&lt;br /&gt;
                if (text &amp;amp;&amp;amp; viewer.textContent !== text) {&lt;br /&gt;
                    viewer.textContent = text;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;1&#039;);&lt;br /&gt;
                viewer.scrollTop = currentScrollTop;&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;0&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocIndicatorForShell() {&lt;br /&gt;
        var existing = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            document.body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (existing &amp;amp;&amp;amp; existing.parentNode) {&lt;br /&gt;
            existing.parentNode.removeChild(existing);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        removeSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocIndicatorForShell() {&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var extMatch;&lt;br /&gt;
        var ext;&lt;br /&gt;
        var row;&lt;br /&gt;
        var box;&lt;br /&gt;
        var meta;&lt;br /&gt;
        var label;&lt;br /&gt;
        var type;&lt;br /&gt;
        var title;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var main;&lt;br /&gt;
&lt;br /&gt;
        if (!document.body || !isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = readCurrentPageNameForShell();&lt;br /&gt;
        extMatch = pageName.match(/\.(css|js)$/i);&lt;br /&gt;
        ext = extMatch ? extMatch[1].toUpperCase() : &#039;DOC&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.classList.add(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        row = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!row) {&lt;br /&gt;
            row = document.createElement(&#039;div&#039;);&lt;br /&gt;
            row.id = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
            row.className = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
&lt;br /&gt;
            box = document.createElement(&#039;div&#039;);&lt;br /&gt;
            box.className = &#039;clbi-system-doc-indicator&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta = document.createElement(&#039;div&#039;);&lt;br /&gt;
            meta.className = &#039;clbi-system-doc-meta&#039;;&lt;br /&gt;
&lt;br /&gt;
            label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            label.className = &#039;clbi-system-doc-label&#039;;&lt;br /&gt;
            label.textContent = &#039;SYSTEM DOCUMENT&#039;;&lt;br /&gt;
&lt;br /&gt;
            type = document.createElement(&#039;span&#039;);&lt;br /&gt;
            type.className = &#039;clbi-system-doc-type&#039;;&lt;br /&gt;
&lt;br /&gt;
            title = document.createElement(&#039;div&#039;);&lt;br /&gt;
            title.className = &#039;clbi-system-doc-title&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta.appendChild(label);&lt;br /&gt;
            meta.appendChild(type);&lt;br /&gt;
            box.appendChild(meta);&lt;br /&gt;
            box.appendChild(title);&lt;br /&gt;
            row.appendChild(box);&lt;br /&gt;
&lt;br /&gt;
            anchor = getSystemDocOutputForShell();&lt;br /&gt;
            main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (anchor &amp;amp;&amp;amp; anchor.parentNode) {&lt;br /&gt;
                anchor.parentNode.insertBefore(row, anchor);&lt;br /&gt;
            } else if (main) {&lt;br /&gt;
                main.insertBefore(row, main.firstChild);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        type = row.querySelector(&#039;.clbi-system-doc-type&#039;);&lt;br /&gt;
        title = row.querySelector(&#039;.clbi-system-doc-title&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (type) type.textContent = ext;&lt;br /&gt;
        if (title) title.textContent = pageName;&lt;br /&gt;
&lt;br /&gt;
        renderSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var PAGE_TITLE_TARGET_SELECTORS = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var pageShellObserverStarted = false;&lt;br /&gt;
    var pageShellObserverTimer = null;&lt;br /&gt;
&lt;br /&gt;
    function setPageTitleDomHidden(hidden) {&lt;br /&gt;
        var nodes = document.querySelectorAll(PAGE_TITLE_TARGET_SELECTORS.join(&#039;,&#039;));&lt;br /&gt;
&lt;br /&gt;
        nodes.forEach(function (node) {&lt;br /&gt;
            if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
            if (hidden) {&lt;br /&gt;
                node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
                node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
                node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClasses() {&lt;br /&gt;
        var body = document.body;&lt;br /&gt;
        var isSystemPage;&lt;br /&gt;
&lt;br /&gt;
        if (!body) return;&lt;br /&gt;
&lt;br /&gt;
        isSystemPage = isBackendOrSystemPageForShell();&lt;br /&gt;
&lt;br /&gt;
        body.classList.remove(&#039;page-title-hidden&#039;, &#039;page-title-visible&#039;, &#039;backend-system-page&#039;, &#039;anecdote-namespace-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            removeSystemDocIndicatorForShell();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isAnecdoteNamespaceForShell()) {&lt;br /&gt;
            body.classList.add(&#039;anecdote-namespace-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;, &#039;backend-system-page&#039;, &#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
            renderSystemDocIndicatorForShell();&lt;br /&gt;
        } else if (isSystemPage) {&lt;br /&gt;
            body.classList.add(&#039;page-title-visible&#039;, &#039;backend-system-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(false);&lt;br /&gt;
        } else {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClassesDeferred() {&lt;br /&gt;
        applyPageShellClasses();&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 0);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 80);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startPageShellObserver() {&lt;br /&gt;
        var observer;&lt;br /&gt;
&lt;br /&gt;
        if (pageShellObserverStarted || !window.MutationObserver || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
        pageShellObserverStarted = true;&lt;br /&gt;
        observer = new MutationObserver(function (mutations) {&lt;br /&gt;
            var i;&lt;br /&gt;
            var target;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            시스템 CSS/JS 문서는 applyPageShellClasses()가 초기에 한 번&lt;br /&gt;
            인디케이터와 source viewer를 만든 뒤에는 MutationObserver가 다시&lt;br /&gt;
            같은 렌더링을 반복할 필요가 없다. 이 반복이 DevTools에서 body/요소가&lt;br /&gt;
            계속 플래시되는 직접 원인이다.&lt;br /&gt;
            SPA 전환 뒤의 처리는 loadPage()와 wikipage.content hook에서 따로 호출된다.&lt;br /&gt;
            */&lt;br /&gt;
            if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
                for (i = 0; i &amp;lt; mutations.length; i += 1) {&lt;br /&gt;
                    target = mutations[i] &amp;amp;&amp;amp; mutations[i].target;&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        target &amp;amp;&amp;amp;&lt;br /&gt;
                        target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                        (&lt;br /&gt;
                            target.id === &#039;clbi-system-source-viewer&#039; ||&lt;br /&gt;
                            target.id === &#039;clbi-system-doc-indicator-row&#039; ||&lt;br /&gt;
                            (target.closest &amp;amp;&amp;amp; target.closest(&#039;#clbi-system-source-viewer, #clbi-system-doc-indicator-row&#039;))&lt;br /&gt;
                        )&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-doc-indicator-row&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-source-viewer&#039;)&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (pageShellObserverTimer) return;&lt;br /&gt;
&lt;br /&gt;
            pageShellObserverTimer = window.setTimeout(function () {&lt;br /&gt;
                pageShellObserverTimer = null;&lt;br /&gt;
                applyPageShellClasses();&lt;br /&gt;
            }, 50);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        observer.observe(document.body, {&lt;br /&gt;
            childList: true,&lt;br /&gt;
            subtree: true&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {&lt;br /&gt;
            applyPageShellClassesDeferred();&lt;br /&gt;
            startPageShellObserver();&lt;br /&gt;
        });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyPageShellClassesDeferred();&lt;br /&gt;
        startPageShellObserver();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(applyPageShellClassesDeferred);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_PAGE_SHELL = {&lt;br /&gt;
        refresh: applyPageShellClasses,&lt;br /&gt;
        isBackendOrSystemPage: isBackendOrSystemPageForShell,&lt;br /&gt;
        isSystemAssetPage: isMediaWikiSystemAssetPageForShell,&lt;br /&gt;
        renderSystemDocIndicator: renderSystemDocIndicatorForShell,&lt;br /&gt;
        removeSystemDocIndicator: removeSystemDocIndicatorForShell,&lt;br /&gt;
        refreshSystemDocSourceViewer: renderSystemDocSourceViewerForShell&lt;br /&gt;
    };&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
function loadLangScript(done) {&lt;br /&gt;
    $.getScript(&#039;/index.php?title=미디어위키:Lang.js&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;)&lt;br /&gt;
        .done(function() {&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        })&lt;br /&gt;
        .fail(function(a, b, c) {&lt;br /&gt;
            console.error(&#039;Lang.js load failed:&#039;, b, c);&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initHalftoneBackground() {&lt;br /&gt;
    try {&lt;br /&gt;
        initWebGLHalftoneBackground();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;WebGL halftone background failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initWebGLHalftoneBackground() {&lt;br /&gt;
    var canvasId = &#039;site-halftone-bg&#039;;&lt;br /&gt;
    var existing = document.getElementById(canvasId);&lt;br /&gt;
    var canvas = existing || document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    var halftoneState = window.SiteHalftoneBackgroundState || (window.SiteHalftoneBackgroundState = { runId: 0 });&lt;br /&gt;
    var runId = halftoneState.runId + 1;&lt;br /&gt;
&lt;br /&gt;
    halftoneState.runId = runId;&lt;br /&gt;
&lt;br /&gt;
    if (!existing) {&lt;br /&gt;
        canvas.id = canvasId;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        document.body.insertBefore(canvas, document.body.firstChild || null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canvas.style.position = &#039;fixed&#039;;&lt;br /&gt;
    canvas.style.inset = &#039;0&#039;;&lt;br /&gt;
    canvas.style.width = &#039;100vw&#039;;&lt;br /&gt;
    canvas.style.height = &#039;100vh&#039;;&lt;br /&gt;
    canvas.style.pointerEvents = &#039;none&#039;;&lt;br /&gt;
    canvas.style.background = &#039;#000000&#039;;&lt;br /&gt;
    canvas.style.display = &#039;block&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!canvas.getAttribute(&#039;data-halftone-context-watch&#039;)) {&lt;br /&gt;
        canvas.setAttribute(&#039;data-halftone-context-watch&#039;, &#039;1&#039;);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;, function (event) {&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            canvas.style.display = &#039;none&#039;;&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = true;&lt;br /&gt;
                window.SiteHalftoneBackgroundState.runId += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }, false);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextrestored&#039;, function () {&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = false;&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(initWebGLHalftoneBackground, 0);&lt;br /&gt;
        }, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gl = canvas.getContext(&#039;webgl&#039;, {&lt;br /&gt;
        alpha: false,&lt;br /&gt;
        antialias: false,&lt;br /&gt;
        depth: false,&lt;br /&gt;
        stencil: false,&lt;br /&gt;
        preserveDrawingBuffer: false,&lt;br /&gt;
        powerPreference: &#039;low-power&#039;&lt;br /&gt;
    }) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!gl) {&lt;br /&gt;
        canvas.style.display = &#039;none&#039;;&lt;br /&gt;
        console.warn(&#039;WebGL background unavailable.&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexSrc = [&lt;br /&gt;
        &#039;attribute vec2 a_position;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_position, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var fragmentSrc = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_resolution;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;const float TAU = 6.28318530718;&#039;,&lt;br /&gt;
        &#039;float gaussian(float v, float r) {&#039;,&lt;br /&gt;
        &#039;  return exp(-((v * v) / max(0.0001, r * r)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float hash(vec2 p) {&#039;,&lt;br /&gt;
        &#039;  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float bucketAlpha(float a) {&#039;,&lt;br /&gt;
        &#039;  float i = floor(a * 9.0);&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 1.0) return 0.040;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 2.0) return 0.080;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 3.0) return 0.135;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 4.0) return 0.210;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 5.0) return 0.310;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 6.0) return 0.430;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 7.0) return 0.580;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 8.0) return 0.760;&#039;,&lt;br /&gt;
        &#039;  return 0.920;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 frag = gl_FragCoord.xy;&#039;,&lt;br /&gt;
        &#039;  float spacing = 5.0;&#039;,&lt;br /&gt;
        &#039;  float dotSize = 1.08;&#039;,&lt;br /&gt;
        &#039;  vec2 grid = floor(frag / spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 inCell = mod(frag, spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 dotOrigin = vec2(1.0, 1.0);&#039;,&lt;br /&gt;
        &#039;  vec2 dotCenter = dotOrigin + vec2(dotSize * 0.5);&#039;,&lt;br /&gt;
        &#039;  vec2 local = abs(inCell - dotCenter);&#039;,&lt;br /&gt;
        &#039;  float noise = hash(grid);&#039;,&lt;br /&gt;
        &#039;  float size = dotSize + noise * 0.18;&#039;,&lt;br /&gt;
        &#039;  float dotMask = 1.0 - smoothstep(size * 0.5, size * 0.5 + 0.22, max(local.x, local.y));&#039;,&lt;br /&gt;
        &#039;  vec2 uv = frag / u_resolution;&#039;,&lt;br /&gt;
        &#039;  float centerLine = 0.50 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 1.32 + 0.08) * TAU) * 0.070 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 3.18 + 0.34) * TAU) * 0.030;&#039;,&lt;br /&gt;
        &#039;  float u = uv.x - centerLine;&#039;,&lt;br /&gt;
        &#039;  float absU = abs(u);&#039;,&lt;br /&gt;
        &#039;  float sideLift = smoothstep(0.065, 0.44, absU);&#039;,&lt;br /&gt;
        &#039;  float valley = gaussian(u, 0.150);&#039;,&lt;br /&gt;
        &#039;  float t = u_time;&#039;,&lt;br /&gt;
        &#039;  float leftRibbonCenter = -0.28 + sin((uv.y * 3.20 + 0.12) * TAU) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float rightRibbonCenter = 0.27 + sin((uv.y * 2.85 + 0.56) * TAU) * 0.055;&#039;,&lt;br /&gt;
        &#039;  float leftRibbon = gaussian(u - leftRibbonCenter, 0.105);&#039;,&lt;br /&gt;
        &#039;  float rightRibbon = gaussian(u - rightRibbonCenter, 0.110);&#039;,&lt;br /&gt;
        &#039;  float foldedU = u +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 4.40 + 0.22) * TAU) * 0.050 * (0.3 + sideLift) +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 7.20 + uv.x * 1.10) * TAU) * 0.022;&#039;,&lt;br /&gt;
        &#039;  float verticalFold = pow(0.5 + 0.5 * cos(((foldedU * 3.05) + (sin(uv.y * TAU * 2.35) * 0.18)) * TAU), 2.5);&#039;,&lt;br /&gt;
        &#039;  float diagonalFold = pow(0.5 + 0.5 * cos(((foldedU * 1.80) - (uv.y * 1.12) + 0.18) * TAU), 2.1);&#039;,&lt;br /&gt;
        &#039;  float waist = gaussian(uv.y - 0.50, 0.25) * gaussian(absU - 0.20, 0.19);&#039;,&lt;br /&gt;
        &#039;  float grain = (noise - 0.5) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float staticField =&#039;,&lt;br /&gt;
        &#039;    0.055 +&#039;,&lt;br /&gt;
        &#039;    sideLift * 0.210 +&#039;,&lt;br /&gt;
        &#039;    (leftRibbon + rightRibbon) * 0.145 +&#039;,&lt;br /&gt;
        &#039;    verticalFold * (0.055 + sideLift * 0.115) +&#039;,&lt;br /&gt;
        &#039;    diagonalFold * 0.045 +&#039;,&lt;br /&gt;
        &#039;    waist * 0.060 -&#039;,&lt;br /&gt;
        &#039;    valley * 0.150 +&#039;,&lt;br /&gt;
        &#039;    grain;&#039;,&lt;br /&gt;
        &#039;  float alpha = staticField;&#039;,&lt;br /&gt;
        &#039;  alpha += 0.115 * (leftRibbon + rightRibbon) * sin(t * 0.00030 + ((uv.y * 1.9) + sideLift * 0.4) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.095 * verticalFold * (0.4 + sideLift) * sin(t * 0.00041 + ((uv.y * 2.7) + foldedU * 0.65) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.070 * waist * sin(t * 0.00053 + ((uv.y * 3.1) - absU * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.060 * (1.0 - valley) * diagonalFold * sin(t * 0.00067 + ((uv.y * 1.4) + uv.x * 0.6) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.038 * (0.35 + sideLift) * (0.35 + noise) * sin(t * 0.00079 + ((uv.y * 4.6) + noise * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha = bucketAlpha(clamp(alpha, 0.025, 0.96));&#039;,&lt;br /&gt;
        &#039;  float value = alpha * dotMask;&#039;,&lt;br /&gt;
        &#039;  gl_FragColor = vec4(vec3(0.8862745 * value), 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function compileShader(type, source) {&lt;br /&gt;
        var shader = gl.createShader(type);&lt;br /&gt;
        gl.shaderSource(shader, source);&lt;br /&gt;
        gl.compileShader(shader);&lt;br /&gt;
&lt;br /&gt;
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {&lt;br /&gt;
            console.error(&#039;WebGL shader compile error:&#039;, gl.getShaderInfoLog(shader));&lt;br /&gt;
            gl.deleteShader(shader);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return shader;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexShader = compileShader(gl.VERTEX_SHADER, vertexSrc);&lt;br /&gt;
    var fragmentShader = compileShader(gl.FRAGMENT_SHADER, fragmentSrc);&lt;br /&gt;
&lt;br /&gt;
    if (!vertexShader || !fragmentShader) return;&lt;br /&gt;
&lt;br /&gt;
    var program = gl.createProgram();&lt;br /&gt;
    gl.attachShader(program, vertexShader);&lt;br /&gt;
    gl.attachShader(program, fragmentShader);&lt;br /&gt;
    gl.linkProgram(program);&lt;br /&gt;
&lt;br /&gt;
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {&lt;br /&gt;
        console.error(&#039;WebGL program link error:&#039;, gl.getProgramInfoLog(program));&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var positionLoc = gl.getAttribLocation(program, &#039;a_position&#039;);&lt;br /&gt;
    var resolutionLoc = gl.getUniformLocation(program, &#039;u_resolution&#039;);&lt;br /&gt;
    var timeLoc = gl.getUniformLocation(program, &#039;u_time&#039;);&lt;br /&gt;
&lt;br /&gt;
    var buffer = gl.createBuffer();&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([&lt;br /&gt;
        -1, -1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
         1,  1&lt;br /&gt;
    ]), gl.STATIC_DRAW);&lt;br /&gt;
&lt;br /&gt;
    gl.useProgram(program);&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.enableVertexAttribArray(positionLoc);&lt;br /&gt;
    gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
    function resize() {&lt;br /&gt;
        var dpr = Math.min(window.devicePixelRatio || 1, 1.5);&lt;br /&gt;
        var cssW = Math.max(1, window.innerWidth || document.documentElement.clientWidth || 1);&lt;br /&gt;
        var cssH = Math.max(1, window.innerHeight || document.documentElement.clientHeight || 1);&lt;br /&gt;
        var w = Math.max(1, Math.floor(cssW * dpr));&lt;br /&gt;
        var h = Math.max(1, Math.floor(cssH * dpr));&lt;br /&gt;
&lt;br /&gt;
        if (canvas.width !== w || canvas.height !== h) {&lt;br /&gt;
            canvas.width = w;&lt;br /&gt;
            canvas.height = h;&lt;br /&gt;
            canvas.style.width = cssW + &#039;px&#039;;&lt;br /&gt;
            canvas.style.height = cssH + &#039;px&#039;;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var prefersReducedMotion = false;&lt;br /&gt;
    try {&lt;br /&gt;
        prefersReducedMotion = window.matchMedia &amp;amp;&amp;amp; window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches;&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneNode() {&lt;br /&gt;
        return document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneState() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            return &#039;none&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) {&lt;br /&gt;
            return &#039;active&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundBusy === &#039;function&#039; &amp;amp;&amp;amp; instance.isHalftoneBackgroundBusy()) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;present&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function shouldSkipHalftoneDrawForGlobe() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) return false;&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Background pause/resume — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * While the nations globe is being held, keep the last rendered halftone&lt;br /&gt;
         * frame on screen and pause the halftone clock.  The important detail is&lt;br /&gt;
         * that this is not a wall-clock catch-up: skipped seconds are not replayed&lt;br /&gt;
         * or fast-forwarded when the pointer is released.&lt;br /&gt;
         */&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) return true;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; instance.dragging) return true;&lt;br /&gt;
&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundHardBusy === &#039;function&#039;) {&lt;br /&gt;
            return instance.isHalftoneBackgroundHardBusy();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) return true;&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFrameInterval() {&lt;br /&gt;
        if (prefersReducedMotion) {&lt;br /&gt;
            return 1000;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Nations background parity — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * The nations page used to permanently lower the halftone cadence while&lt;br /&gt;
         * the globe existed.  Profiling showed that the steady halftone draw is&lt;br /&gt;
         * normally cheap; the real contention happens during globe drag, first&lt;br /&gt;
         * WebGL texture upload, and long layout/render tasks.  Keep the same&lt;br /&gt;
         * full-quality cadence as ordinary pages, and skip only the frames that&lt;br /&gt;
         * would directly collide with an active/busy globe moment.&lt;br /&gt;
         */&lt;br /&gt;
        return 66;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var lastFrame = 0;&lt;br /&gt;
    var startTime = performance.now();&lt;br /&gt;
    var visualTime = 0;&lt;br /&gt;
    var lastVisualNow = 0;&lt;br /&gt;
&lt;br /&gt;
    function draw(now) {&lt;br /&gt;
        var state = getNationsGlobeHalftoneState();&lt;br /&gt;
        var interval = getFrameInterval();&lt;br /&gt;
        var perf = window.InteractionPerf;&lt;br /&gt;
        var delta;&lt;br /&gt;
&lt;br /&gt;
        if (!lastVisualNow) {&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
        }&lt;br /&gt;
        delta = Math.max(0, Math.min(120, now - lastVisualNow));&lt;br /&gt;
        lastVisualNow = now;&lt;br /&gt;
        visualTime += delta;&lt;br /&gt;
&lt;br /&gt;
        function runDraw() {&lt;br /&gt;
            resize();&lt;br /&gt;
&lt;br /&gt;
            gl.clearColor(0, 0, 0, 1);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.uniform2f(resolutionLoc, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform1f(timeLoc, visualTime);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLES, 0, 6);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (perf &amp;amp;&amp;amp; typeof perf.measureSync === &#039;function&#039;) {&lt;br /&gt;
            return perf.measureSync(&#039;background halftone draw&#039;, { globeState: state, interval: interval }, runDraw);&lt;br /&gt;
        }&lt;br /&gt;
        return runDraw();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(now) {&lt;br /&gt;
        if (halftoneState.runId !== runId || halftoneState.contextLost) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(render);&lt;br /&gt;
&lt;br /&gt;
        if (document.hidden) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (now - lastFrame &amp;lt; getFrameInterval()) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (shouldSkipHalftoneDrawForGlobe()) {&lt;br /&gt;
            lastFrame = now;&lt;br /&gt;
            halftoneState.lastGlobeBusySkipAt = now;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Pause the halftone clock while a hard-busy globe frame owns the&lt;br /&gt;
             * budget.  Without this, the next visible background frame catches&lt;br /&gt;
             * up to wall-clock time and the pattern appears to skip forward.&lt;br /&gt;
             * Updating lastVisualNow here makes the animation resume from the&lt;br /&gt;
             * last visible moment instead of replaying the hidden gap.&lt;br /&gt;
             */&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        draw(now);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    draw(performance.now());&lt;br /&gt;
    requestAnimationFrame(render);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SVG_BELL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_BELL_DOT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell-dot&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M11.68 2.009A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673c-.824-.85-1.678-1.731-2.21-3.348&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;18&amp;quot; cy=&amp;quot;5&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LIST = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-list&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 5h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 12h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 19h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 5h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 12h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 19h13&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LANGUAGES = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-languages&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;m5 8 6 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m4 14 6-6 2-3&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M2 5h12&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 2h1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m22 22-5-10-5 10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 18h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_POWER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-power&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 2v10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.4 6.6a9 9 0 1 1-12.77.04&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SETTINGS = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-settings&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_TEXT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-text&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 8h8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 12h10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 16h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_EYE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-eye&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_NEWSPAPER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-newspaper&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M15 18h-5&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 14h-8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-4 0v-9a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;8&amp;quot; height=&amp;quot;4&amp;quot; x=&amp;quot;10&amp;quot; y=&amp;quot;6&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_GREAT_WALL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-great-wall lucide lucide-paint-roller&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;rect width=&amp;quot;16&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;2&amp;quot; y=&amp;quot;2&amp;quot; rx=&amp;quot;2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M10 16v-2a2 2 0 0 1 2-2h8a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;4&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;8&amp;quot; y=&amp;quot;16&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_TROPHY = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-trophy&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 9h1.5a1 1 0 0 0 0-5H18&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9H4.5a1 1 0 0 1 0-5H6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_PACKAGE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-package&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 3v6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M16.76 3a2 2 0 0 1 1.8 1.1l2.23 4.479a2 2 0 0 1 .21.891V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9.472a2 2 0 0 1 .211-.894L5.45 4.1A2 2 0 0 1 7.24 3z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.054 9.013h17.893&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
var PROFILE_RENDER_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function invalidateProfileRender() {&lt;br /&gt;
    PROFILE_RENDER_TOKEN++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    initHalftoneBackground();&lt;br /&gt;
&lt;br /&gt;
// ── 하단 Plank 단축키 가이드 ──&lt;br /&gt;
function escapeClbiBottomGuideHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiBottomGuidePageName() {&lt;br /&gt;
    var pageName = window.mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) {&lt;br /&gt;
        pageName = window.location.pathname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return String(pageName)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsShortcutContext() {&lt;br /&gt;
    var pageName = readClbiBottomGuidePageName();&lt;br /&gt;
&lt;br /&gt;
    return pageName === &#039;시대&#039; ||&lt;br /&gt;
        pageName === &#039;Era&#039; ||&lt;br /&gt;
        !!document.querySelector(&#039;.clbi-nations-panel-stack, .clbi-nations-globe-window, .clbi-nations-tabpanel&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiWikiEditShortcutContext() {&lt;br /&gt;
    var action = window.mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgAction&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    var search = String(window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
    return action === &#039;edit&#039; ||&lt;br /&gt;
        action === &#039;submit&#039; ||&lt;br /&gt;
        /(?:^|[?&amp;amp;])action=(?:edit|submit)(?:&amp;amp;|$)/.test(search) ||&lt;br /&gt;
        !!document.querySelector(&#039;#editform, #wpSave, input[name=&amp;quot;wpSave&amp;quot;], button[name=&amp;quot;wpSave&amp;quot;]&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function isClbiNationListManagerShortcutContext() {&lt;br /&gt;
    return !!document.querySelector(&#039;.nation-list-manager&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiBottomShortcutItems() {&lt;br /&gt;
    if (isClbiWikiEditShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;변경사항 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationListManagerShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;국가 목록 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Q&#039;, label: &#039;이전 시대&#039; },&lt;br /&gt;
            { key: &#039;E&#039;, label: &#039;다음 시대&#039; },&lt;br /&gt;
            { key: &#039;Shift+Q&#039;, label: &#039;이전 연도&#039; },&lt;br /&gt;
            { key: &#039;Shift+E&#039;, label: &#039;다음 연도&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+Q&#039;, label: &#039;이전 대륙&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+E&#039;, label: &#039;다음 대륙&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return [];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderClbiBottomShortcutGuide() {&lt;br /&gt;
    var guide = document.getElementById(&#039;clbi-bottom-shortcut-guide&#039;);&lt;br /&gt;
    var items;&lt;br /&gt;
    var html;&lt;br /&gt;
&lt;br /&gt;
    if (!guide) return;&lt;br /&gt;
&lt;br /&gt;
    items = getClbiBottomShortcutItems();&lt;br /&gt;
&lt;br /&gt;
    if (!items.length) {&lt;br /&gt;
        guide.classList.add(&#039;is-empty&#039;);&lt;br /&gt;
        guide.innerHTML = &#039;&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    guide.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    html = &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-list&amp;quot;&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        html += &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-key&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.key) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-label&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    html += &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    guide.innerHTML = html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiBottomPlankHtml(wrapId, navId, mainId) {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-bottom-shortcut-guide&amp;quot; class=&amp;quot;is-empty&amp;quot; aria-label=&amp;quot;단축키 안내&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
&lt;br /&gt;
function getClbiNationsTabAtPointer(tabpanel) {&lt;br /&gt;
    var element;&lt;br /&gt;
    var tab;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return null;&lt;br /&gt;
    if (CLBI_NATIONS_LAST_POINTER_X === null || CLBI_NATIONS_LAST_POINTER_Y === null) return null;&lt;br /&gt;
    if (typeof document.elementFromPoint !== &#039;function&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
    element = document.elementFromPoint(CLBI_NATIONS_LAST_POINTER_X, CLBI_NATIONS_LAST_POINTER_Y);&lt;br /&gt;
    if (!element) return null;&lt;br /&gt;
&lt;br /&gt;
    tab = element.closest ? element.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (!tab || !tabpanel.contains(tab)) return null;&lt;br /&gt;
&lt;br /&gt;
    return tab;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateClbiNationsPointerHover(tabpanel, forceSuppress) {&lt;br /&gt;
    var tab = getClbiNationsTabAtPointer(tabpanel);&lt;br /&gt;
    var suppressContinent;&lt;br /&gt;
    var tabContinent;&lt;br /&gt;
    var tabIsActive;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
&lt;br /&gt;
    if (!tab) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabContinent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    tabIsActive = tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (tabIsActive) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    suppressContinent = tabpanel.getAttribute(&#039;data-clbi-hover-suppress-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (forceSuppress || !suppressContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-hover-suppress-continent&#039;, tabContinent);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (suppressContinent === tabContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * The pointer actually moved onto another tab after the keyboard switch.&lt;br /&gt;
     * At that point this is no longer stale browser :hover; let normal hover work.&lt;br /&gt;
     */&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateAllClbiNationsPointerHovers() {&lt;br /&gt;
    var panels = document.querySelectorAll(&#039;.clbi-nations-tabpanel.is-keyboard-switching&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        if (isClbiNationsPanelOwnedTabpanel(tabpanel)) return;&lt;br /&gt;
        validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsPanelOwnedTabpanel(tabpanel) {&lt;br /&gt;
    /*&lt;br /&gt;
     * Mouse continent tab regression guard.&lt;br /&gt;
     * -------------------------------------&lt;br /&gt;
     * Detached SPA preparation serializes attributes/classes but it cannot&lt;br /&gt;
     * serialize DOM event listeners or JS properties.  The NationsPanel fix&lt;br /&gt;
     * therefore uses the DOM property CLBI_NationsPanelOwned /&lt;br /&gt;
     * CLBI_NationsTabPanelBound as the real ownership marker.&lt;br /&gt;
     *&lt;br /&gt;
     * Do NOT treat data-nations-tabpanel-ready or clbi-nations-continent-cache&lt;br /&gt;
     * as ownership here.  Those can survive innerHTML insertion while the real&lt;br /&gt;
     * click listeners were lost, causing Common.js to delegate to a panel that&lt;br /&gt;
     * NationsPanel has not rebound yet.  The fallback below must remain able to&lt;br /&gt;
     * handle mouse clicks until NationsPanel reclaims the live DOM node.&lt;br /&gt;
     */&lt;br /&gt;
    return !!(tabpanel &amp;amp;&amp;amp; tabpanel.CLBI_NationsPanelOwned);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsLiveContinentActive(tabpanel, continent) {&lt;br /&gt;
    var tab;&lt;br /&gt;
    var panel;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !continent) return false;&lt;br /&gt;
&lt;br /&gt;
    tab = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panel = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent-panel&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return !!(&lt;br /&gt;
        tab &amp;amp;&amp;amp;&lt;br /&gt;
        panel &amp;amp;&amp;amp;&lt;br /&gt;
        (tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.classList.contains(&#039;is-active&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.getAttribute(&#039;aria-hidden&#039;) !== &#039;true&#039; &amp;amp;&amp;amp;&lt;br /&gt;
        !panel.hasAttribute(&#039;hidden&#039;)&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function activateClbiNationsContinent(tabpanel, targetContinent, options) {&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var panels;&lt;br /&gt;
    var skipOwner = !!(options &amp;amp;&amp;amp; options.skipOwner);&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !targetContinent) return false;&lt;br /&gt;
&lt;br /&gt;
    if (!skipOwner &amp;amp;&amp;amp; isClbiNationsPanelOwnedTabpanel(tabpanel)) {&lt;br /&gt;
        var owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
&lt;br /&gt;
        if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (owner.activateContinent(targetContinent, {&lt;br /&gt;
                    source: &#039;common-legacy-click&#039;,&lt;br /&gt;
                    panel: tabpanel&lt;br /&gt;
                })) {&lt;br /&gt;
                    return true;&lt;br /&gt;
                }&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Ownership markers can be stale during SPA/hydration edge cases.  If&lt;br /&gt;
         * the owner API is missing or refuses the live panel, do not drop the&lt;br /&gt;
         * mouse click.  Fall through to the local fallback so pointer users are&lt;br /&gt;
         * never left with keyboard-only continent tabs.&lt;br /&gt;
         */&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    panels = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
    if (!tabs.length || !panels.length) return false;&lt;br /&gt;
&lt;br /&gt;
    tabs.forEach(function (tab) {&lt;br /&gt;
        var active = tab.getAttribute(&#039;data-continent&#039;) === targetContinent;&lt;br /&gt;
        tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
        tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panels.forEach(function (panel) {&lt;br /&gt;
        var active = panel.getAttribute(&#039;data-continent-panel&#039;) === targetContinent;&lt;br /&gt;
        panel.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
        if (active) {&lt;br /&gt;
            panel.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            panel.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.sync === &#039;function&#039;) window.CLBI_DECORATIONS.sync();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    validateClbiNationsPointerHover(tabpanel, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
    tabpanel.classList.remove(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
    tabpanel.removeAttribute(&#039;data-clbi-hover-suppress-continent&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveClbiNationsContinent(direction) {&lt;br /&gt;
    var tabpanel = document.querySelector(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var activeIndex;&lt;br /&gt;
    var nextIndex;&lt;br /&gt;
    var target;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return false;&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsPanelOwnedTabpanel(tabpanel) &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI_NATIONS_PANEL &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI_NATIONS_PANEL.moveContinent === &#039;function&#039;) {&lt;br /&gt;
        return !!window.CLBI_NATIONS_PANEL.moveContinent(direction, {&lt;br /&gt;
            source: &#039;common-legacy-keyboard&#039;,&lt;br /&gt;
            panel: tabpanel&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
    activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
        return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
    nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
    target = tabs[nextIndex].getAttribute(&#039;data-continent&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (activateClbiNationsContinent(tabpanel, target)) {&lt;br /&gt;
        setClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
        }, 0);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsTabpanelControls(root) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var panels = scope.querySelectorAll(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        /*&lt;br /&gt;
         * Common.js is only a safety fallback for continent tabs; NationsPanel&lt;br /&gt;
         * is the primary owner.  This guard must be a DOM property, not a&lt;br /&gt;
         * serialized attribute.  SPA warm prepaint can preserve&lt;br /&gt;
         * data-clbi-nations-tabs-ready=&amp;quot;1&amp;quot; while losing the actual event&lt;br /&gt;
         * listeners, which was the root cause of mouse clicks no longer moving&lt;br /&gt;
         * continents while keyboard shortcuts still worked.&lt;br /&gt;
         */&lt;br /&gt;
        if (tabpanel.CLBI_NationsCommonTabsBound) return;&lt;br /&gt;
        tabpanel.CLBI_NationsCommonTabsBound = true;&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-nations-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerdown&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
            if (activateClbiNationsContinent(tabpanel, tab.getAttribute(&#039;data-continent&#039;))) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
            var handled = false;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsContinent(-1);&lt;br /&gt;
            else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsContinent(1);&lt;br /&gt;
&lt;br /&gt;
            if (handled) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                event.stopPropagation();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function handleClbiNationsContinentDocumentClick(event) {&lt;br /&gt;
    var target;&lt;br /&gt;
    var tab;&lt;br /&gt;
    var tabpanel;&lt;br /&gt;
    var continent;&lt;br /&gt;
    var owner;&lt;br /&gt;
    var handled = false;&lt;br /&gt;
&lt;br /&gt;
    if (!event || event.__clbiNationsContinentHandled) return;&lt;br /&gt;
    target = event.target;&lt;br /&gt;
    if (!target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
    tab = target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;);&lt;br /&gt;
    if (!tab) return;&lt;br /&gt;
&lt;br /&gt;
    tabpanel = tab.closest ? tab.closest(&#039;.clbi-nations-tabpanel&#039;) : null;&lt;br /&gt;
    if (!tabpanel || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
    continent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    if (!continent) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Final live-DOM mouse delegate.&lt;br /&gt;
     * --------------------------------&lt;br /&gt;
     * The continent tab UI has two initialization phases: detached preparation&lt;br /&gt;
     * for seamless SPA entry, then live DOM hydration.  Detached preparation can&lt;br /&gt;
     * leave serialized &amp;quot;ready&amp;quot; attributes behind while losing event listeners.&lt;br /&gt;
     * Keyboard shortcuts still work because they call the public NationsPanel&lt;br /&gt;
     * API directly, but mouse clicks depend on live listeners.  This capturing&lt;br /&gt;
     * delegate is intentionally independent of per-panel ready attributes: it&lt;br /&gt;
     * always resolves the clicked live tab and hands it to NationsPanel first,&lt;br /&gt;
     * then falls back to the local Common.js switcher if needed.&lt;br /&gt;
     */&lt;br /&gt;
    event.__clbiNationsContinentHandled = true;&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
    owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
    if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
        try {&lt;br /&gt;
            handled = !!owner.activateContinent(continent, {&lt;br /&gt;
                source: &#039;mouse-click-live-delegate&#039;,&lt;br /&gt;
                panel: tabpanel&lt;br /&gt;
            });&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Owner API stale-cache guard.&lt;br /&gt;
         *&lt;br /&gt;
         * A previous regression came from NationsPanel returning true after it&lt;br /&gt;
         * updated cached/detached tab nodes instead of the clicked live tab. A&lt;br /&gt;
         * true return value alone is therefore not enough for mouse input. Verify&lt;br /&gt;
         * the live DOM that received the click. If it did not become active,&lt;br /&gt;
         * bypass the owner and run the Common.js live query fallback directly.&lt;br /&gt;
         */&lt;br /&gt;
        if (handled &amp;amp;&amp;amp; !isClbiNationsLiveContinentActive(tabpanel, continent)) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!handled) {&lt;br /&gt;
        handled = activateClbiNationsContinent(tabpanel, continent, { skipOwner: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (handled) {&lt;br /&gt;
        try { playStaticSound(); } catch (err2) {}&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) event.stopImmediatePropagation();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsGlobalClickDelegate() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-global-click-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-global-click-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleClbiNationsContinentDocumentClick, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsPointerHoverValidation() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointermove&#039;, function (event) {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = event.clientX;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = event.clientY;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiBottomShortcutSystem(root) {&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsGlobalClickDelegate();&lt;br /&gt;
    initClbiNationsTabpanelControls(root || document);&lt;br /&gt;
    initClbiNationsPointerHoverValidation();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// ── 상·하단 네비게이션 바 ──&lt;br /&gt;
function buildClbiNavHtml(position) {&lt;br /&gt;
    var isBottom = position === &#039;bottom&#039;;&lt;br /&gt;
    var base = isBottom ? &#039;clbi-bottom&#039; : &#039;clbi-top&#039;;&lt;br /&gt;
    var shortBase = isBottom ? &#039;clbi-bnav&#039; : &#039;clbi-tnav&#039;;&lt;br /&gt;
    var wrapId = base + &#039;-nav-wrap&#039;;&lt;br /&gt;
    var navId = base + &#039;-nav&#039;;&lt;br /&gt;
    var mainId = base + &#039;-nav-main&#039;;&lt;br /&gt;
    var tabsId = base + &#039;-nav-tabs&#039;;&lt;br /&gt;
    var searchId = base + &#039;-nav-search&#039;;&lt;br /&gt;
    var inputId = isBottom ? &#039;clbi-bottom-search-input&#039; : &#039;clbi-top-search-input&#039;;&lt;br /&gt;
    var worldId = shortBase + &#039;-worldbuilding&#039;;&lt;br /&gt;
    var subId = isBottom ? &#039;clbi-bottom-sub-worldbuilding&#039; : &#039;clbi-sub-worldbuilding&#039;;&lt;br /&gt;
    var subInnerId = subId + &#039;-inner&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (isBottom) {&lt;br /&gt;
        return buildClbiBottomPlankHtml(wrapId, navId, mainId);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + tabsId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/대문&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-main-menu-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;메인 메뉴&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-project-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;프로젝트&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-top-nav-item&amp;quot; id=&amp;quot;&#039; + worldId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-worldbuilding-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;세계관&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-arrow&amp;quot;&amp;gt;▾&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    (isBottom ? &#039;&#039; : (&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;&#039; + searchId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;&#039; + inputId + &#039;&amp;quot; placeholder=&amp;quot;검색...&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                    )) +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + subId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + subInnerId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-tnav-sub-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/시대&amp;quot;&amp;gt;시대&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/설정&amp;quot;&amp;gt;설정&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/기업_및_공동체&amp;quot;&amp;gt;기업 및 공동체&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/군_정치집단&amp;quot;&amp;gt;군, 정치집단&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/인물&amp;quot;&amp;gt;인물&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeClbiShellDomOrder() {&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var canvas = document.getElementById(&#039;site-halftone-bg&#039;);&lt;br /&gt;
    var anchor;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var topRect;&lt;br /&gt;
    var wrapperRect;&lt;br /&gt;
    var needsRecovery;&lt;br /&gt;
    var expectedWrapperTop;&lt;br /&gt;
    var host;&lt;br /&gt;
&lt;br /&gt;
    if (!contentWrapper || !topNav || !bottomNav || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    CLBI shell can live inside a Liberty &amp;lt;section&amp;gt;.  Some skins/layouts give that&lt;br /&gt;
    section a flow context that lets the top nav visually overlap the content&lt;br /&gt;
    wrapper even when DOM sibling order is correct.  Mark the common parent and&lt;br /&gt;
    let Layout.css force a simple vertical flow for the shell.&lt;br /&gt;
    */&lt;br /&gt;
    host = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp;&lt;br /&gt;
        contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? contentWrapper.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (host) {&lt;br /&gt;
        host.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    viewportH = window.innerHeight || document.documentElement.clientHeight || 0;&lt;br /&gt;
    topRect = topNav.getBoundingClientRect();&lt;br /&gt;
    wrapperRect = contentWrapper.getBoundingClientRect();&lt;br /&gt;
    expectedWrapperTop = topRect.bottom + 8;&lt;br /&gt;
&lt;br /&gt;
    needsRecovery = false;&lt;br /&gt;
&lt;br /&gt;
    if (viewportH &amp;gt; 0) {&lt;br /&gt;
        if (topRect.top &amp;gt;= viewportH * 0.55) needsRecovery = true;&lt;br /&gt;
        if (wrapperRect.top &amp;gt;= viewportH * 0.60) needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (topRect.top &amp;gt; 240 || wrapperRect.top &amp;gt; 320) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (wrapperRect.top &amp;lt; expectedWrapperTop - 1) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!needsRecovery) {&lt;br /&gt;
        document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    anchor = canvas &amp;amp;&amp;amp; canvas.parentNode === document.body&lt;br /&gt;
        ? canvas.nextSibling&lt;br /&gt;
        : document.body.firstChild;&lt;br /&gt;
&lt;br /&gt;
    document.body.insertBefore(topNav, anchor);&lt;br /&gt;
    document.body.insertBefore(contentWrapper, topNav.nextSibling);&lt;br /&gt;
    document.body.insertBefore(bottomNav, contentWrapper.nextSibling);&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.normalizeClbiShellDomOrder = normalizeClbiShellDomOrder;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureClbiVerticalScaleHost() {&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var host;&lt;br /&gt;
    var existing;&lt;br /&gt;
    var parent;&lt;br /&gt;
&lt;br /&gt;
    if (!topNav || !contentWrapper || !bottomNav || !document.body) return null;&lt;br /&gt;
&lt;br /&gt;
    existing = document.getElementById(&#039;clbi-shell-scale-host&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (existing &amp;amp;&amp;amp; existing.contains(topNav) &amp;amp;&amp;amp; existing.contains(contentWrapper) &amp;amp;&amp;amp; existing.contains(bottomNav)) {&lt;br /&gt;
        existing.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        return existing;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    parent = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp; contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? topNav.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (parent &amp;amp;&amp;amp; parent !== document.body) {&lt;br /&gt;
        parent.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        parent.id = parent.id || &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
        return parent;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host = existing || document.createElement(&#039;div&#039;);&lt;br /&gt;
    host.id = &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
    host.className = &#039;clbi-shell-host&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!host.parentNode) {&lt;br /&gt;
        document.body.insertBefore(host, topNav);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host.appendChild(topNav);&lt;br /&gt;
    host.appendChild(contentWrapper);&lt;br /&gt;
    host.appendChild(bottomNav);&lt;br /&gt;
&lt;br /&gt;
    return host;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiRootPx(name, fallback) {&lt;br /&gt;
    var raw = &#039;&#039;;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        raw = getComputedStyle(document.documentElement).getPropertyValue(name);&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    value = parseFloat(raw);&lt;br /&gt;
    return isFinite(value) &amp;amp;&amp;amp; value &amp;gt; 0 ? value : fallback;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiShellVerticalScale() {&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    var host;&lt;br /&gt;
    var topInner;&lt;br /&gt;
    var bottomInner;&lt;br /&gt;
    var thresholdH;&lt;br /&gt;
    var gap;&lt;br /&gt;
    var outerGapTotal;&lt;br /&gt;
    var baseStageH;&lt;br /&gt;
    var stageW;&lt;br /&gt;
    var stageH;&lt;br /&gt;
    var viewportW;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var availableW;&lt;br /&gt;
    var availableH;&lt;br /&gt;
    var topH;&lt;br /&gt;
    var bottomH;&lt;br /&gt;
    var contentH;&lt;br /&gt;
    var widthScale;&lt;br /&gt;
    var heightScale;&lt;br /&gt;
    var scale;&lt;br /&gt;
    var shouldScale;&lt;br /&gt;
&lt;br /&gt;
    if (!root || !body) return;&lt;br /&gt;
&lt;br /&gt;
    host = ensureClbiVerticalScaleHost();&lt;br /&gt;
    if (!host) return;&lt;br /&gt;
&lt;br /&gt;
    thresholdH = readClbiRootPx(&#039;--clbi-vertical-scale-threshold-h&#039;, 1080);&lt;br /&gt;
    gap = readClbiRootPx(&#039;--layout-gap&#039;, 8);&lt;br /&gt;
    outerGapTotal = gap * 2;&lt;br /&gt;
    baseStageH = Math.max(420, thresholdH - outerGapTotal);&lt;br /&gt;
    stageW = readClbiRootPx(&#039;--layout-shell-w&#039;, 1880);&lt;br /&gt;
&lt;br /&gt;
    viewportW = Math.max(320, window.innerWidth || document.documentElement.clientWidth || stageW);&lt;br /&gt;
    viewportH = Math.max(320, window.innerHeight || document.documentElement.clientHeight || thresholdH);&lt;br /&gt;
    availableW = Math.max(240, viewportW - outerGapTotal);&lt;br /&gt;
    availableH = Math.max(240, viewportH - outerGapTotal);&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    평상시에는 기존 세로 채움 레이아웃을 기준으로 삼는다.&lt;br /&gt;
    세로가 기준점보다 작아지는 경우에는 기준 높이를 고정점으로 삼고,&lt;br /&gt;
    가로만 부족한 경우에는 현재 사용 가능한 세로 높이를 고정점으로 삼는다.&lt;br /&gt;
    이렇게 해야 가로 부족으로 scale에 들어갈 때 본문 높이가 갑자기 접히지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    stageH = availableH &amp;gt;= baseStageH ? availableH : baseStageH;&lt;br /&gt;
&lt;br /&gt;
    topInner = document.getElementById(&#039;clbi-top-nav&#039;);&lt;br /&gt;
    bottomInner = document.getElementById(&#039;clbi-bottom-nav&#039;);&lt;br /&gt;
&lt;br /&gt;
    topH = topInner ? Math.ceil(topInner.offsetHeight || topInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    bottomH = bottomInner ? Math.ceil(bottomInner.offsetHeight || bottomInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    contentH = Math.max(360, Math.floor(stageH - topH - bottomH - (gap * 2)));&lt;br /&gt;
&lt;br /&gt;
    widthScale = availableW &amp;lt; stageW ? availableW / stageW : 1;&lt;br /&gt;
    heightScale = availableH &amp;lt; baseStageH ? availableH / baseStageH : 1;&lt;br /&gt;
    scale = Math.min(1, widthScale, heightScale);&lt;br /&gt;
    scale = Math.max(0.50, Math.min(1, Math.floor(scale * 1000) / 1000));&lt;br /&gt;
    shouldScale = scale &amp;lt; 0.999;&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-w&#039;, stageW + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-h&#039;, Math.floor(stageH) + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-content-h&#039;, contentH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-shell-scale&#039;, String(scale));&lt;br /&gt;
&lt;br /&gt;
    body.classList.toggle(&#039;clbi-shell-vertical-scale&#039;, shouldScale);&lt;br /&gt;
&lt;br /&gt;
    if (shouldScale) {&lt;br /&gt;
        resetLeftRecentAdaptiveState();&lt;br /&gt;
        resetLeftBillboardAdaptiveState();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
window.updateClbiShellVerticalScale = updateClbiShellVerticalScale;&lt;br /&gt;
&lt;br /&gt;
var $contentWrapper = $(&#039;.content-wrapper&#039;).first();&lt;br /&gt;
&lt;br /&gt;
if ($contentWrapper.length) {&lt;br /&gt;
    $(&#039;#clbi-top-nav-wrap, #clbi-bottom-nav-wrap&#039;).remove();&lt;br /&gt;
    $contentWrapper.before(buildClbiNavHtml(&#039;top&#039;));&lt;br /&gt;
    $contentWrapper.after(buildClbiNavHtml(&#039;bottom&#039;));&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsTabpanelControls(document);&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
function runClbiShellMetricsBatch() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var topH = 0;&lt;br /&gt;
    var bottomH = 0;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    if (top) {&lt;br /&gt;
        topH = Math.ceil(top.getBoundingClientRect().height || top.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (bottom) {&lt;br /&gt;
        bottomH = Math.ceil(bottom.getBoundingClientRect().height || bottom.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-top-nav-outer-h&#039;, topH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-bottom-nav-outer-h&#039;, bottomH + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (typeof updateClbiShellVerticalScale === &#039;function&#039;) {&lt;br /&gt;
        updateClbiShellVerticalScale();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestClbiShellMetricsFrame() {&lt;br /&gt;
    if (CLBI_SHELL_METRICS_RAF !== null) return;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = window.requestAnimationFrame&lt;br /&gt;
        ? window.requestAnimationFrame(runClbiShellMetricsBatch)&lt;br /&gt;
        : window.setTimeout(runClbiShellMetricsBatch, 16);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiShellMetrics() {&lt;br /&gt;
    requestClbiShellMetricsFrame();&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 0);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 80);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 240);&lt;br /&gt;
}&lt;br /&gt;
function watchClbiShellMetrics() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var observer;&lt;br /&gt;
&lt;br /&gt;
    scheduleClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
    $(window).on(&#039;resize orientationchange&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    $(window).on(&#039;pageshow.clbiShellScale focus.clbiShellScale&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
        if (!document.hidden) scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
    $(window).on(&#039;resize.clbiLeftBillboard orientationchange.clbiLeftBillboard&#039;, scheduleLeftSidebarVerticalFit);&lt;br /&gt;
    $(window).on(&#039;resize.clbiRecentViewport orientationchange.clbiRecentViewport&#039;, function () { scheduleAdaptiveLeftRecentItems(); scheduleClbiContentBottomGap(); });&lt;br /&gt;
    $(window).on(&#039;resize.clbiContentBottomGap orientationchange.clbiContentBottomGap&#039;, scheduleClbiContentBottomGap);&lt;br /&gt;
&lt;br /&gt;
    if (window.ResizeObserver) {&lt;br /&gt;
        observer = new ResizeObserver(scheduleClbiShellMetrics);&lt;br /&gt;
        if (top) observer.observe(top);&lt;br /&gt;
        if (bottom) observer.observe(bottom);&lt;br /&gt;
        window.CLBI_SHELL_RESIZE_OBSERVER = observer;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindClbiWorldbuildingToggle(buttonSelector, menuSelector) {&lt;br /&gt;
    $(buttonSelector).on(&#039;click&#039;, function() {&lt;br /&gt;
        var $menu = $(menuSelector);&lt;br /&gt;
        var $btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        $menu.toggleClass(&#039;worldbuilding-open&#039;);&lt;br /&gt;
        $btn.toggleClass(&#039;clbi-tnav-active&#039;, $menu.hasClass(&#039;worldbuilding-open&#039;));&lt;br /&gt;
        scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-tnav-worldbuilding&#039;, &#039;#clbi-sub-worldbuilding&#039;);&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-bnav-worldbuilding&#039;, &#039;#clbi-bottom-sub-worldbuilding&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-top-search-input, #clbi-bottom-search-input&#039;).on(&#039;keydown&#039;, function(e) {&lt;br /&gt;
    if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
        var q = $(this).val().trim();&lt;br /&gt;
        if (q) window.location.href = &#039;/index.php?search=&#039; + encodeURIComponent(q);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
        initClbiBottomShortcutSystem($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
watchClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// 페이지 전환 사운드&lt;br /&gt;
var transitionSound = new Audio(&#039;/index.php?title=특수:Redirect/file/Sfx-ui-001.mp3&#039;);&lt;br /&gt;
&lt;br /&gt;
(function() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
    transitionSound.volume = sfxOn ? master * sfx : 0;&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
function playStaticSound() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!sfxOn) return;&lt;br /&gt;
&lt;br /&gt;
    transitionSound.volume = master * sfx;&lt;br /&gt;
    transitionSound.currentTime = 0;&lt;br /&gt;
    transitionSound.play();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 현재 언어 감지&lt;br /&gt;
function getCurrentLang() {&lt;br /&gt;
    var langData = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    return langData ? (langData.getAttribute(&#039;data-lang&#039;) || &#039;ko&#039;) : &#039;ko&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizePageName(value) {&lt;br /&gt;
    return String(value || &#039;&#039;)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildWikiPath(title) {&lt;br /&gt;
    return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLangShortCode(lang) {&lt;br /&gt;
    var map = { ko: &#039;KR&#039;, en: &#039;EN&#039;, zh: &#039;ZH&#039;, ja: &#039;JA&#039;, ru: &#039;RU&#039;, es: &#039;ES&#039; };&lt;br /&gt;
    return map[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLanguageTargetTitle(lang) {&lt;br /&gt;
    var data = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    if (!data || !lang) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var keys = [&lt;br /&gt;
        &#039;data-&#039; + lang,&lt;br /&gt;
        &#039;data-page-&#039; + lang,&lt;br /&gt;
        &#039;data-title-&#039; + lang,&lt;br /&gt;
        &#039;data-target-&#039; + lang,&lt;br /&gt;
        &#039;data-lang-&#039; + lang&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var value = data.getAttribute(keys[i]);&lt;br /&gt;
        if (value) return value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeClbiHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var SIDEBAR_LANG_SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_TITLE = &#039;MediaWiki:LanguageStatus.json&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;English&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;Русский&#039;,&lt;br /&gt;
    es: &#039;Español&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_DIAL_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;ENG&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;РУС&#039;,&lt;br /&gt;
    es: &#039;ESP&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_VALUES = {&lt;br /&gt;
    available: true,&lt;br /&gt;
    wip: true,&lt;br /&gt;
    unavailable: true&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageStatusRegistry = {};&lt;br /&gt;
var sidebarLanguageStatusLoaded = false;&lt;br /&gt;
var sidebarLanguageStatusLoading = false;&lt;br /&gt;
var sidebarLanguageStatusCallbacks = [];&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageState = {&lt;br /&gt;
    order: [&#039;ko&#039;, &#039;en&#039;, &#039;zh&#039;, &#039;ja&#039;, &#039;ru&#039;, &#039;es&#039;],&lt;br /&gt;
    currentLang: &#039;ko&#039;,&lt;br /&gt;
    baseIndex: 0,&lt;br /&gt;
    selectedIndex: 0,&lt;br /&gt;
    rotation: 0,&lt;br /&gt;
    dragging: false,&lt;br /&gt;
    dragMoved: false,&lt;br /&gt;
    dragStartX: 0,&lt;br /&gt;
    dragStartY: 0,&lt;br /&gt;
    dragStartRotation: 0,&lt;br /&gt;
    dragAxis: null,&lt;br /&gt;
    pointerCaptured: false,&lt;br /&gt;
    lastX: 0,&lt;br /&gt;
    lastTime: 0,&lt;br /&gt;
    releaseVelocity: 0,&lt;br /&gt;
    suppressClickUntil: 0,&lt;br /&gt;
    raf: null,&lt;br /&gt;
    pendingRotation: 0,&lt;br /&gt;
    snapTimer: null,&lt;br /&gt;
    inertiaRaf: null,&lt;br /&gt;
    navigateTimer: null,&lt;br /&gt;
    bound: false,&lt;br /&gt;
    boundElement: null,&lt;br /&gt;
    rotor: null,&lt;br /&gt;
    cx: 101,&lt;br /&gt;
    cy: 119,&lt;br /&gt;
    outerR: 109,&lt;br /&gt;
    innerR: 28,&lt;br /&gt;
    sectorAngle: 30,&lt;br /&gt;
    halfSector: 15,&lt;br /&gt;
    repeats: 8,&lt;br /&gt;
    dragSensitivity: 0.58,&lt;br /&gt;
    maxSpinVelocity: 1.75,&lt;br /&gt;
    minSpinVelocity: 0.055,&lt;br /&gt;
    spinDecel: 0.00185&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function createSidebarLanguageSvgEl(tag) {&lt;br /&gt;
    return document.createElementNS(SIDEBAR_LANG_SVG_NS, tag);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageIndex(index) {&lt;br /&gt;
    var length = sidebarLanguageState.order.length;&lt;br /&gt;
    var normalized = index % length;&lt;br /&gt;
    return normalized &amp;lt; 0 ? normalized + length : normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageDialName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_DIAL_LABELS[lang] || getSidebarLanguageName(lang);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageStatusValue(value) {&lt;br /&gt;
    value = String(value == null ? &#039;&#039; : value).toLowerCase().trim();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_STATUS_VALUES[value] ? value : &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusPageKey() {&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
&lt;br /&gt;
    return normalized || raw || &#039;대문&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusEntry() {&lt;br /&gt;
    var registry = sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    var pages = registry.pages &amp;amp;&amp;amp; typeof registry.pages === &#039;object&#039; ? registry.pages : registry;&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
    var title = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var keys = [&lt;br /&gt;
        normalized,&lt;br /&gt;
        raw,&lt;br /&gt;
        raw.replace(/_/g, &#039; &#039;),&lt;br /&gt;
        normalized.replace(/ /g, &#039;_&#039;),&lt;br /&gt;
        title,&lt;br /&gt;
        title.replace(/_/g, &#039; &#039;)&lt;br /&gt;
    ];&lt;br /&gt;
    var i;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; keys.length; i += 1) {&lt;br /&gt;
        if (keys[i] &amp;amp;&amp;amp; pages[keys[i]] &amp;amp;&amp;amp; typeof pages[keys[i]] === &#039;object&#039;) {&lt;br /&gt;
            return pages[keys[i]];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {};&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusOverride(lang) {&lt;br /&gt;
    var entry = getSidebarLanguageStatusEntry();&lt;br /&gt;
    return normalizeSidebarLanguageStatusValue(entry[lang]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function flushSidebarLanguageStatusCallbacks() {&lt;br /&gt;
    var callbacks = sidebarLanguageStatusCallbacks.slice();&lt;br /&gt;
    sidebarLanguageStatusCallbacks.length = 0;&lt;br /&gt;
&lt;br /&gt;
    callbacks.forEach(function(callback) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
            callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadSidebarLanguageStatusRegistry(callback, force) {&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        sidebarLanguageStatusCallbacks.push(callback);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoaded &amp;amp;&amp;amp; !force) {&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoading) return;&lt;br /&gt;
&lt;br /&gt;
    sidebarLanguageStatusLoading = true;&lt;br /&gt;
&lt;br /&gt;
    function finishLanguageStatus(parsed) {&lt;br /&gt;
        sidebarLanguageStatusRegistry = parsed &amp;amp;&amp;amp; typeof parsed === &#039;object&#039; ? parsed : {};&lt;br /&gt;
        sidebarLanguageStatusLoaded = true;&lt;br /&gt;
        sidebarLanguageStatusLoading = false;&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!force &amp;amp;&amp;amp; window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.fetchJsonRef === &#039;function&#039;) {&lt;br /&gt;
        window.EntryStore.fetchJsonRef(SIDEBAR_LANGUAGE_STATUS_TITLE, { noStore: false })&lt;br /&gt;
            .then(function (parsed) { finishLanguageStatus(parsed); })&lt;br /&gt;
            .catch(function () { finishLanguageStatus({}); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    (function () {&lt;br /&gt;
        var url = mw.util.getUrl(SIDEBAR_LANGUAGE_STATUS_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, SIDEBAR_LANGUAGE_STATUS_TITLE);&lt;br /&gt;
        }&lt;br /&gt;
        $.ajax({&lt;br /&gt;
            url: url,&lt;br /&gt;
            dataType: &#039;text&#039;,&lt;br /&gt;
            cache: true&lt;br /&gt;
        }).done(function(text) {&lt;br /&gt;
            var parsed = {};&lt;br /&gt;
&lt;br /&gt;
            try {&lt;br /&gt;
                parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                console.error(&#039;LanguageStatus.json parse failed:&#039;, err);&lt;br /&gt;
                parsed = {};&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            finishLanguageStatus(parsed);&lt;br /&gt;
        }).fail(function() {&lt;br /&gt;
            finishLanguageStatus({});&lt;br /&gt;
        });&lt;br /&gt;
    })();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.CLBI_LANGUAGE_STATUS = {&lt;br /&gt;
    title: SIDEBAR_LANGUAGE_STATUS_TITLE,&lt;br /&gt;
    languages: sidebarLanguageState.order.slice(),&lt;br /&gt;
    labels: SIDEBAR_LANGUAGE_LABELS,&lt;br /&gt;
    dialLabels: SIDEBAR_LANGUAGE_DIAL_LABELS,&lt;br /&gt;
    getPageKey: getSidebarLanguageStatusPageKey,&lt;br /&gt;
    getRegistry: function() {&lt;br /&gt;
        return sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    },&lt;br /&gt;
    reload: function(callback) {&lt;br /&gt;
        sidebarLanguageStatusLoaded = false;&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            renderSidebarLanguageBox();&lt;br /&gt;
            if (typeof callback === &#039;function&#039;) callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }, true);&lt;br /&gt;
    },&lt;br /&gt;
    refreshDial: function() {&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageMeta(lang) {&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var targetTitle = getLanguageTargetTitle(lang);&lt;br /&gt;
    var isCurrent = lang === currentLang;&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        lang: lang,&lt;br /&gt;
        code: getLangShortCode(lang),&lt;br /&gt;
        name: getSidebarLanguageName(lang),&lt;br /&gt;
        dialName: getSidebarLanguageDialName(lang),&lt;br /&gt;
        targetTitle: targetTitle,&lt;br /&gt;
        isCurrent: isCurrent,&lt;br /&gt;
        canMove: !!targetTitle &amp;amp;&amp;amp; !isCurrent&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatus(meta) {&lt;br /&gt;
    var override;&lt;br /&gt;
&lt;br /&gt;
    if (!meta) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (meta.isCurrent) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-current&#039;,&lt;br /&gt;
            label: &#039;CURRENT&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    override = getSidebarLanguageStatusOverride(meta.lang);&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;wip&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;WIP&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;unavailable&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;available&#039; || meta.targetTitle) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: meta.targetTitle ? &#039;is-ready&#039; : &#039;is-locked&#039;,&lt;br /&gt;
            label: meta.targetTitle ? &#039;AVAILABLE&#039; : &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: !!meta.targetTitle&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        className: &#039;is-locked&#039;,&lt;br /&gt;
        label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
        canApply: false&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguageRad(deg) {&lt;br /&gt;
    return (deg * Math.PI) / 180;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguagePointAt(radius, deg) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var angle = sidebarLanguageRad(deg);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        x: state.cx + Math.sin(angle) * radius,&lt;br /&gt;
        y: state.cy - Math.cos(angle) * radius&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageSectorPath(start, end) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var p1 = sidebarLanguagePointAt(state.outerR, start);&lt;br /&gt;
    var p2 = sidebarLanguagePointAt(state.outerR, end);&lt;br /&gt;
    var p3 = sidebarLanguagePointAt(state.innerR, end);&lt;br /&gt;
    var p4 = sidebarLanguagePointAt(state.innerR, start);&lt;br /&gt;
    var largeArc = Math.abs(end - start) &amp;gt; 180 ? 1 : 0;&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;M&#039;, p1.x.toFixed(3), p1.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.outerR, state.outerR, 0, largeArc, 1, p2.x.toFixed(3), p2.y.toFixed(3),&lt;br /&gt;
        &#039;L&#039;, p3.x.toFixed(3), p3.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.innerR, state.innerR, 0, largeArc, 0, p4.x.toFixed(3), p4.y.toFixed(3),&lt;br /&gt;
        &#039;Z&#039;&lt;br /&gt;
    ].join(&#039; &#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageShellPath() {&lt;br /&gt;
    return getSidebarLanguageSectorPath(-68, 68);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageByStep(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        index: index,&lt;br /&gt;
        meta: getSidebarLanguageMeta(state.order[index])&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewIndex() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    return normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewMeta() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    return getSidebarLanguageMeta(state.order[getSidebarLanguagePreviewIndex()]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeSidebarLanguageSector(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var item = getSidebarLanguageByStep(step);&lt;br /&gt;
    var group = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    var path = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    var label = createSidebarLanguageSvgEl(&#039;text&#039;);&lt;br /&gt;
    var labelY = state.cy - 78;&lt;br /&gt;
    var angle = step * state.sectorAngle;&lt;br /&gt;
&lt;br /&gt;
    group.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-group&#039;);&lt;br /&gt;
    group.setAttribute(&#039;data-step&#039;, String(step));&lt;br /&gt;
    group.setAttribute(&#039;data-index&#039;, String(item.index));&lt;br /&gt;
    group.setAttribute(&#039;data-lang&#039;, item.meta.lang);&lt;br /&gt;
    group.setAttribute(&#039;transform&#039;, &#039;rotate(&#039; + angle + &#039; &#039; + state.cx + &#039; &#039; + state.cy + &#039;)&#039;);&lt;br /&gt;
&lt;br /&gt;
    path.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector&#039;);&lt;br /&gt;
    path.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
&lt;br /&gt;
    label.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-label&#039;);&lt;br /&gt;
    label.setAttribute(&#039;x&#039;, String(state.cx));&lt;br /&gt;
    label.setAttribute(&#039;y&#039;, String(labelY + 5));&lt;br /&gt;
    label.textContent = item.meta.dialName || item.meta.name;&lt;br /&gt;
&lt;br /&gt;
    group.appendChild(path);&lt;br /&gt;
    group.appendChild(label);&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        if (sidebarLanguageState.dragging || performance.now() &amp;lt; sidebarLanguageState.suppressClickUntil) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        snapSidebarLanguageToStep(parseInt(group.getAttribute(&#039;data-step&#039;) || &#039;0&#039;, 10), true);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return group;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageWheel() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var svg;&lt;br /&gt;
    var defs;&lt;br /&gt;
    var clip;&lt;br /&gt;
    var clipPath;&lt;br /&gt;
    var shadowBlur;&lt;br /&gt;
    var blur;&lt;br /&gt;
    var fixedDepthGradient;&lt;br /&gt;
    var shell;&lt;br /&gt;
    var clipped;&lt;br /&gt;
    var rotor;&lt;br /&gt;
    var fixedDepthPath;&lt;br /&gt;
    var fixedFocus;&lt;br /&gt;
    var shadowSoft;&lt;br /&gt;
    var shadowHard;&lt;br /&gt;
    var rim;&lt;br /&gt;
    var pointer;&lt;br /&gt;
    var tri;&lt;br /&gt;
    var line;&lt;br /&gt;
    var step;&lt;br /&gt;
&lt;br /&gt;
    if (!fan) return;&lt;br /&gt;
&lt;br /&gt;
    fan.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    svg = createSidebarLanguageSvgEl(&#039;svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fan-svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 202 150&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;xMidYMid meet&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;role&#039;, &#039;img&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;aria-label&#039;, &#039;언어 선택 다이얼&#039;);&lt;br /&gt;
&lt;br /&gt;
    defs = createSidebarLanguageSvgEl(&#039;defs&#039;);&lt;br /&gt;
&lt;br /&gt;
    clip = createSidebarLanguageSvgEl(&#039;clipPath&#039;);&lt;br /&gt;
    clip.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fan-clip&#039;);&lt;br /&gt;
    clipPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    clipPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    clip.appendChild(clipPath);&lt;br /&gt;
&lt;br /&gt;
    shadowBlur = createSidebarLanguageSvgEl(&#039;filter&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-shadow-blur&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;x&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;y&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;width&#039;, &#039;140%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;height&#039;, &#039;140%&#039;);&lt;br /&gt;
    blur = createSidebarLanguageSvgEl(&#039;feGaussianBlur&#039;);&lt;br /&gt;
    blur.setAttribute(&#039;stdDeviation&#039;, &#039;3&#039;);&lt;br /&gt;
    shadowBlur.appendChild(blur);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthGradient = createSidebarLanguageSvgEl(&#039;linearGradient&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x2&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y2&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    [&lt;br /&gt;
        [&#039;0%&#039;, &#039;#ffffff&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;34%&#039;, &#039;#ffffff&#039;, &#039;0.006&#039;],&lt;br /&gt;
        [&#039;58%&#039;, &#039;#000000&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;100%&#039;, &#039;#000000&#039;, &#039;0.250&#039;]&lt;br /&gt;
    ].forEach(function(item) {&lt;br /&gt;
        var stop = createSidebarLanguageSvgEl(&#039;stop&#039;);&lt;br /&gt;
        stop.setAttribute(&#039;offset&#039;, item[0]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-color&#039;, item[1]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-opacity&#039;, item[2]);&lt;br /&gt;
        fixedDepthGradient.appendChild(stop);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    defs.appendChild(clip);&lt;br /&gt;
    defs.appendChild(shadowBlur);&lt;br /&gt;
    defs.appendChild(fixedDepthGradient);&lt;br /&gt;
    svg.appendChild(defs);&lt;br /&gt;
&lt;br /&gt;
    shell = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;class&#039;, &#039;sidebar-lang-shell&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shell);&lt;br /&gt;
&lt;br /&gt;
    clipped = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    clipped.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    rotor = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;class&#039;, &#039;sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
&lt;br /&gt;
    for (step = -state.repeats; step &amp;lt;= state.repeats; step += 1) {&lt;br /&gt;
        rotor.appendChild(makeSidebarLanguageSector(step));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    clipped.appendChild(rotor);&lt;br /&gt;
    svg.appendChild(clipped);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(fixedDepthPath);&lt;br /&gt;
&lt;br /&gt;
    fixedFocus = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-focus&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
    svg.appendChild(fixedFocus);&lt;br /&gt;
&lt;br /&gt;
    shadowSoft = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-soft&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowSoft);&lt;br /&gt;
&lt;br /&gt;
    shadowHard = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-hard&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowHard);&lt;br /&gt;
&lt;br /&gt;
    rim = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;class&#039;, &#039;sidebar-lang-rim&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(rim);&lt;br /&gt;
&lt;br /&gt;
    pointer = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-pointer&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    tri = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-triangle&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;d&#039;, &#039;M &#039; + (state.cx - 10) + &#039; 10 L &#039; + (state.cx + 10) + &#039; 10 L &#039; + state.cx + &#039; 26 Z&#039;);&lt;br /&gt;
    pointer.appendChild(tri);&lt;br /&gt;
&lt;br /&gt;
    line = createSidebarLanguageSvgEl(&#039;line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;x1&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;x2&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;y1&#039;, &#039;24&#039;);&lt;br /&gt;
    line.setAttribute(&#039;y2&#039;, &#039;112&#039;);&lt;br /&gt;
    pointer.appendChild(line);&lt;br /&gt;
&lt;br /&gt;
    svg.appendChild(pointer);&lt;br /&gt;
    fan.appendChild(svg);&lt;br /&gt;
&lt;br /&gt;
    state.rotor = rotor;&lt;br /&gt;
    setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateSidebarLanguageDial() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var meta = getSidebarLanguagePreviewMeta();&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
    var selectedValue = document.getElementById(&#039;clbi-sidebar-lang-selected-value&#039;);&lt;br /&gt;
    var availabilityPanel = document.getElementById(&#039;clbi-sidebar-lang-availability-panel&#039;);&lt;br /&gt;
    var availabilityValue = document.getElementById(&#039;clbi-sidebar-lang-availability-value&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (selectedValue) {&lt;br /&gt;
        selectedValue.textContent = meta.name;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityPanel) {&lt;br /&gt;
        availabilityPanel.classList.remove(&#039;is-ready&#039;, &#039;is-current&#039;, &#039;is-locked&#039;);&lt;br /&gt;
        availabilityPanel.classList.add(status.className);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityValue) {&lt;br /&gt;
        availabilityValue.textContent = status.label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (apply) {&lt;br /&gt;
        apply.classList.toggle(&#039;is-disabled&#039;, !status.canApply);&lt;br /&gt;
        apply.setAttribute(&#039;aria-disabled&#039;, status.canApply ? &#039;false&#039; : &#039;true&#039;);&lt;br /&gt;
        apply.setAttribute(&#039;aria-label&#039;, status.canApply ? (meta.name + &#039; 적용&#039;) : (meta.isCurrent ? &#039;현재 언어&#039; : &#039;사용할 수 없는 언어&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (selector) {&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-lang&#039;, meta.lang);&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-code&#039;, meta.code);&lt;br /&gt;
        selector.classList.toggle(&#039;is-current&#039;, meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-ready&#039;, status.canApply);&lt;br /&gt;
        selector.classList.toggle(&#039;is-locked&#039;, !status.canApply &amp;amp;&amp;amp; !meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-dragging&#039;, !!state.dragging);&lt;br /&gt;
        selector.classList.toggle(&#039;is-spinning&#039;, !!state.inertiaRaf);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        meta: meta,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageRotation(value, animate) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.rotation = value;&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (!state.rotor) return;&lt;br /&gt;
&lt;br /&gt;
    if (animate) {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-snapping&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.rotor.style.transform = &#039;rotate(&#039; + state.rotation.toFixed(3) + &#039;deg)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestSidebarLanguageRotation(value) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.pendingRotation = value;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) return;&lt;br /&gt;
&lt;br /&gt;
    state.raf = requestAnimationFrame(function() {&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
        setSidebarLanguageRotation(state.pendingRotation, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function cancelSidebarLanguageSpin() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    if (state.inertiaRaf) {&lt;br /&gt;
        cancelAnimationFrame(state.inertiaRaf);&lt;br /&gt;
        state.inertiaRaf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finishSidebarLanguageSnap(nearestIndex, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    state.baseIndex = normalizeSidebarLanguageIndex(nearestIndex);&lt;br /&gt;
    state.selectedIndex = state.baseIndex;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping is-dragging is-spinning&#039;);&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        callback(getSidebarLanguageMeta(state.order[state.selectedIndex]));&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageToStep(step, animate, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var targetRotation = -step * state.sectorAngle;&lt;br /&gt;
    var nearestIndex = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.selectedIndex = nearestIndex;&lt;br /&gt;
    setSidebarLanguageRotation(targetRotation, !!animate);&lt;br /&gt;
&lt;br /&gt;
    state.snapTimer = setTimeout(function() {&lt;br /&gt;
        finishSidebarLanguageSnap(nearestIndex, callback);&lt;br /&gt;
    }, animate ? 230 : 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageNearest(callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    snapSidebarLanguageToStep(step, true, callback);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function startSidebarLanguageInertiaSpin(initialVelocity) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var velocity;&lt;br /&gt;
    var lastFrame;&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
&lt;br /&gt;
    velocity = Math.max(-state.maxSpinVelocity, Math.min(state.maxSpinVelocity, initialVelocity));&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(velocity) &amp;lt; state.minSpinVelocity) {&lt;br /&gt;
        snapSidebarLanguageNearest();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-spinning&#039;);&lt;br /&gt;
    lastFrame = performance.now();&lt;br /&gt;
&lt;br /&gt;
    function frame(now) {&lt;br /&gt;
        var dt = Math.min(34, Math.max(1, now - lastFrame));&lt;br /&gt;
        var sign = velocity &amp;lt; 0 ? -1 : 1;&lt;br /&gt;
        var nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        state.rotation += velocity * dt;&lt;br /&gt;
        setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
&lt;br /&gt;
        nextSpeed = Math.max(0, Math.abs(velocity) - (state.spinDecel * dt));&lt;br /&gt;
        velocity = sign * nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        if (nextSpeed &amp;lt;= state.minSpinVelocity) {&lt;br /&gt;
            state.inertiaRaf = null;&lt;br /&gt;
            $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleSidebarLanguageNavigation(meta) {&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
&lt;br /&gt;
    if (!meta || !status.canApply) return;&lt;br /&gt;
&lt;br /&gt;
    clearTimeout(sidebarLanguageState.navigateTimer);&lt;br /&gt;
    sidebarLanguageState.navigateTimer = setTimeout(function() {&lt;br /&gt;
        var title = getLanguageTargetTitle(meta.lang);&lt;br /&gt;
&lt;br /&gt;
        if (!title || meta.lang === getCurrentLang()) return;&lt;br /&gt;
&lt;br /&gt;
        window.location.href = buildWikiPath(title);&lt;br /&gt;
    }, 70);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageSelection(lang) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = state.order.indexOf(lang);&lt;br /&gt;
&lt;br /&gt;
    if (index &amp;lt; 0) index = state.order.indexOf(getCurrentLang());&lt;br /&gt;
    if (index &amp;lt; 0) index = 0;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) {&lt;br /&gt;
        cancelAnimationFrame(state.raf);&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.currentLang = lang;&lt;br /&gt;
    state.baseIndex = index;&lt;br /&gt;
    state.selectedIndex = index;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
    state.dragMoved = false;&lt;br /&gt;
    state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveSidebarLanguageSelection(delta) {&lt;br /&gt;
    snapSidebarLanguageToStep(-delta, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindSidebarLanguageSelector() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!selector || !fan || !apply) return;&lt;br /&gt;
&lt;br /&gt;
    if (state.bound &amp;amp;&amp;amp; state.boundElement === selector) return;&lt;br /&gt;
&lt;br /&gt;
    state.bound = true;&lt;br /&gt;
    state.boundElement = selector;&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerdown&#039;, function(e) {&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
        state.dragging = true;&lt;br /&gt;
        state.dragMoved = false;&lt;br /&gt;
        state.dragStartX = e.clientX;&lt;br /&gt;
        state.dragStartY = e.clientY || 0;&lt;br /&gt;
        state.dragStartRotation = state.rotation;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = performance.now();&lt;br /&gt;
        state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
        selector.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
        selector.classList.remove(&#039;is-snapping&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        Vertical page scrolling must stay available when the pointer starts on&lt;br /&gt;
        the language dial. Capture and preventDefault are delayed until a&lt;br /&gt;
        horizontal drag is confirmed.&lt;br /&gt;
        */&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointermove&#039;, function(e) {&lt;br /&gt;
        var now;&lt;br /&gt;
        var totalDx;&lt;br /&gt;
        var totalDy;&lt;br /&gt;
        var frameDx;&lt;br /&gt;
        var dt;&lt;br /&gt;
        var instantVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        totalDx = e.clientX - state.dragStartX;&lt;br /&gt;
        totalDy = (e.clientY || 0) - state.dragStartY;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragAxis &amp;amp;&amp;amp; (Math.abs(totalDx) &amp;gt; 4 || Math.abs(totalDy) &amp;gt; 4)) {&lt;br /&gt;
            state.dragAxis = Math.abs(totalDx) &amp;gt;= Math.abs(totalDy) ? &#039;x&#039; : &#039;y&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (state.dragAxis === &#039;y&#039;) {&lt;br /&gt;
                state.dragging = false;&lt;br /&gt;
                state.dragMoved = false;&lt;br /&gt;
                state.dragAxis = null;&lt;br /&gt;
                state.pointerCaptured = false;&lt;br /&gt;
                selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (fan.setPointerCapture &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
                try {&lt;br /&gt;
                    fan.setPointerCapture(e.pointerId);&lt;br /&gt;
                    state.pointerCaptured = true;&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    state.pointerCaptured = false;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragAxis !== &#039;x&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        now = performance.now();&lt;br /&gt;
        frameDx = e.clientX - state.lastX;&lt;br /&gt;
        dt = Math.max(1, now - state.lastTime);&lt;br /&gt;
&lt;br /&gt;
        if (Math.abs(totalDx) &amp;gt; 3) state.dragMoved = true;&lt;br /&gt;
&lt;br /&gt;
        instantVelocity = (frameDx * state.dragSensitivity) / dt;&lt;br /&gt;
        state.releaseVelocity = (state.releaseVelocity * 0.62) + (instantVelocity * 0.38);&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = now;&lt;br /&gt;
&lt;br /&gt;
        requestSidebarLanguageRotation(state.dragStartRotation + totalDx * state.dragSensitivity);&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function finishDrag(e) {&lt;br /&gt;
        var velocityAge;&lt;br /&gt;
        var throwVelocity;&lt;br /&gt;
        var wasHorizontal;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        wasHorizontal = state.dragAxis === &#039;x&#039;;&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (fan.releasePointerCapture &amp;amp;&amp;amp; state.pointerCaptured &amp;amp;&amp;amp; e &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
            try { fan.releasePointerCapture(e.pointerId); } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
&lt;br /&gt;
        if (!wasHorizontal &amp;amp;&amp;amp; !state.dragMoved) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        velocityAge = performance.now() - state.lastTime;&lt;br /&gt;
        throwVelocity = velocityAge &amp;gt; 120 ? 0 : state.releaseVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(throwVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            startSidebarLanguageInertiaSpin(throwVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerup&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;pointercancel&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;lostpointercapture&#039;, function() {&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(state.releaseVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
            startSidebarLanguageInertiaSpin(state.releaseVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    apply.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        snapSidebarLanguageNearest(function(meta) {&lt;br /&gt;
            scheduleSidebarLanguageNavigation(meta);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    selector.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
        if (e.key === &#039;ArrowLeft&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(-1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;ArrowRight&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;Enter&#039; || e.key === &#039; &#039;) {&lt;br /&gt;
            apply.click();&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageBox() {&lt;br /&gt;
    bindSidebarLanguageSelector();&lt;br /&gt;
    setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
&lt;br /&gt;
    if (!sidebarLanguageStatusLoaded) {&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadRecentChangesList(targetSelector, limit) {&lt;br /&gt;
    var $target = $(targetSelector);&lt;br /&gt;
&lt;br /&gt;
    if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
    var isNewsList = $target.closest(&#039;.clbi-left-news-box&#039;).length &amp;gt; 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.html((t &amp;amp;&amp;amp; t.loading) ? t.loading : &#039;불러오는 중...&#039;);&lt;br /&gt;
&lt;br /&gt;
    $.getJSON(&lt;br /&gt;
        &#039;/api.php?action=query&amp;amp;list=recentchanges&amp;amp;rclimit=&#039; + encodeURIComponent(limit || 5) + &#039;&amp;amp;rcprop=title|timestamp|user&amp;amp;format=json&amp;amp;rcnamespace=0&amp;amp;rctype=edit|new&#039;,&lt;br /&gt;
        function(data) {&lt;br /&gt;
            var items = data &amp;amp;&amp;amp; data.query ? data.query.recentchanges : [];&lt;br /&gt;
            var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (!items || !items.length) {&lt;br /&gt;
                $target.html(&#039;표시할 변경 사항이 없습니다.&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $.each(items, function(i, item) {&lt;br /&gt;
                var label = timeAgo(item.timestamp);&lt;br /&gt;
                var title = item.title || &#039;&#039;;&lt;br /&gt;
                var userName = item.user || &#039;Unknown&#039;;&lt;br /&gt;
                var pageHref = buildWikiPath(title);&lt;br /&gt;
                var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(userName) + &#039;.png&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (isNewsList) {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;news-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;news-recent-avatar&amp;quot; src=&amp;quot;&#039; + escapeHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;news-recent-main&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-meta&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-user&amp;quot;&amp;gt;@&#039; + escapeHtml(userName) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
                } else {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;clbi-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;clbi-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList) {&lt;br /&gt;
                $target.html(&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-recent-viewport&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-recent-stack&amp;quot;&amp;gt;&#039; + html + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
                    ensureNewsBottomFinish();&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                $target.html(html);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList &amp;amp;&amp;amp; typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
                scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $target.find(isNewsList ? &#039;.news-recent-item&#039; : &#039;.clbi-recent-item&#039;).each(function() {&lt;br /&gt;
                var wrap = $(this).find(isNewsList ? &#039;.news-recent-title-wrap&#039; : &#039;.clbi-recent-title-wrap&#039;);&lt;br /&gt;
                var title = $(this).find(isNewsList ? &#039;.news-recent-title&#039; : &#039;.clbi-recent-title&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (!wrap.length || !title.length) return;&lt;br /&gt;
&lt;br /&gt;
                var wrapW = wrap.width();&lt;br /&gt;
                var titleW = title[0].scrollWidth;&lt;br /&gt;
&lt;br /&gt;
                if (titleW &amp;gt; wrapW + 20) {&lt;br /&gt;
                    var duration = titleW / 40;&lt;br /&gt;
&lt;br /&gt;
                    title.css({&lt;br /&gt;
                        animation: &#039;clbi-scroll &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
                        &#039;--scroll-dist&#039;: &#039;-&#039; + (titleW - wrapW + 8) + &#039;px&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    ).fail(function() {&lt;br /&gt;
        var lang = getCurrentLang();&lt;br /&gt;
        var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
&lt;br /&gt;
        $target.html((t &amp;amp;&amp;amp; t.loadFail) ? t.loadFail : &#039;불러오기 실패&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureRecentViewport() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var viewport;&lt;br /&gt;
    var stack;&lt;br /&gt;
    var children;&lt;br /&gt;
&lt;br /&gt;
    if (!list) return null;&lt;br /&gt;
&lt;br /&gt;
    viewport = list.querySelector(&#039;:scope &amp;gt; .news-recent-viewport&#039;);&lt;br /&gt;
    stack = viewport ? viewport.querySelector(&#039;:scope &amp;gt; .news-recent-stack&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (viewport &amp;amp;&amp;amp; stack) return viewport;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(list.children || []);&lt;br /&gt;
&lt;br /&gt;
    viewport = document.createElement(&#039;div&#039;);&lt;br /&gt;
    viewport.className = &#039;news-recent-viewport&#039;;&lt;br /&gt;
&lt;br /&gt;
    stack = document.createElement(&#039;div&#039;);&lt;br /&gt;
    stack.className = &#039;news-recent-stack&#039;;&lt;br /&gt;
&lt;br /&gt;
    children.forEach(function (child) {&lt;br /&gt;
        if (child.classList &amp;amp;&amp;amp; child.classList.contains(&#039;news-recent-viewport&#039;)) return;&lt;br /&gt;
        stack.appendChild(child);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    viewport.appendChild(stack);&lt;br /&gt;
    list.appendChild(viewport);&lt;br /&gt;
&lt;br /&gt;
    return viewport;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ensureNewsBottomFinish() {&lt;br /&gt;
    var newsBox = document.querySelector(&#039;#clbi-left-sidebar .clbi-left-news-box&#039;);&lt;br /&gt;
    var content = newsBox ? newsBox.querySelector(&#039;.clbi-news-box&#039;) : null;&lt;br /&gt;
    var finish;&lt;br /&gt;
&lt;br /&gt;
    if (!content) return null;&lt;br /&gt;
&lt;br /&gt;
    finish = content.querySelector(&#039;:scope &amp;gt; .news-bottom-finish&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!finish) {&lt;br /&gt;
        finish = document.createElement(&#039;div&#039;);&lt;br /&gt;
        finish.className = &#039;news-bottom-finish&#039;;&lt;br /&gt;
        finish.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        content.appendChild(finish);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return finish;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateAdaptiveLeftRecentItems() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: 좌측 사이드는 뉴스 확장형 flex 레이아웃이 높이를 담당한다.&lt;br /&gt;
    이전 adaptive 코드는 항목을 숨기거나 mask/fade를 걸기 위한 것이었으므로&lt;br /&gt;
    여기서는 잔여 상태만 정리하고 DOM 래퍼만 보장한다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftRecentAdaptiveState();&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureRecentViewport === &#039;function&#039;) {&lt;br /&gt;
        ensureRecentViewport();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
        ensureNewsBottomFinish();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleAdaptiveLeftRecentItems() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateAdaptiveLeftRecentItems();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 80);&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateClbiContentBottomGap(iteration) {&lt;br /&gt;
    var content = document.querySelector(&#039;.container-fluid.liberty-content&#039;);&lt;br /&gt;
    var main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var desiredGap = 8;&lt;br /&gt;
    var rootStyle;&lt;br /&gt;
    var scale = 1;&lt;br /&gt;
    var contentRect;&lt;br /&gt;
    var bottomTop;&lt;br /&gt;
    var targetHeight;&lt;br /&gt;
    var currentHeight;&lt;br /&gt;
    var visualGap;&lt;br /&gt;
&lt;br /&gt;
    iteration = iteration || 0;&lt;br /&gt;
&lt;br /&gt;
    if (!content || !main || !bottomNav) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    하단 간격은 scale 모드에서도 같은 기준으로 계산한다.&lt;br /&gt;
    transform:scale()이 걸리면 getBoundingClientRect()는 축소된 화면 좌표를 반환하므로,&lt;br /&gt;
    목표 간격 8px도 scale을 곱한 화면 좌표로 비교하고 다시 design px로 환산한다.&lt;br /&gt;
&lt;br /&gt;
    목표:&lt;br /&gt;
    .liberty-content-main.bottom === #clbi-bottom-nav-wrap.top - 8px&lt;br /&gt;
    */&lt;br /&gt;
    if (document.body &amp;amp;&amp;amp; document.body.classList &amp;amp;&amp;amp; document.body.classList.contains(&#039;clbi-shell-vertical-scale&#039;)) {&lt;br /&gt;
        rootStyle = window.getComputedStyle(document.documentElement);&lt;br /&gt;
        scale = parseFloat(rootStyle.getPropertyValue(&#039;--clbi-shell-scale&#039;)) || 1;&lt;br /&gt;
        scale = Math.max(0.25, scale);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    contentRect = content.getBoundingClientRect();&lt;br /&gt;
    bottomTop = bottomNav.getBoundingClientRect().top;&lt;br /&gt;
    visualGap = desiredGap * scale;&lt;br /&gt;
    targetHeight = Math.floor((bottomTop - contentRect.top - visualGap) / scale);&lt;br /&gt;
    targetHeight = Math.max(120, targetHeight);&lt;br /&gt;
&lt;br /&gt;
    currentHeight = Math.round(content.getBoundingClientRect().height / scale);&lt;br /&gt;
&lt;br /&gt;
    content.style.setProperty(&#039;--clbi-content-extra&#039;, &#039;0px&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;max-height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(currentHeight - targetHeight) &amp;gt;= 1 &amp;amp;&amp;amp; iteration &amp;lt; 4) {&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            updateClbiContentBottomGap(iteration + 1);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiContentBottomGap() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 40);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 120);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 280);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 520);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateLeftBillboardAdaptive() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: Ad는 이미지/CRT 비율을 유지하는 고정 슬롯이다.&lt;br /&gt;
    남는 세로 공간은 뉴스 박스가 흡수하므로, Ad에 하단 finish를 늘리거나&lt;br /&gt;
    title-only 상태로 접는 adaptive 보정은 사용하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftBillboardAdaptiveState();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftBillboardAdaptive() {&lt;br /&gt;
    window.requestAnimationFrame(updateLeftBillboardAdaptive);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 80);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftSidebarVerticalFit() {&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
            scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
            scheduleLeftBillboardAdaptive();&lt;br /&gt;
        }&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시대 문서 전용 왼쪽 사이드바 이미지&lt;br /&gt;
function updateLeftSidebarNationsImage() {&lt;br /&gt;
    $(&#039;#clbi-left-nations-image&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setProfileActionLabel(selector, text) {&lt;br /&gt;
    var target = $(selector);&lt;br /&gt;
    var label = target.find(&#039;.profile-action-label&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (label.length) {&lt;br /&gt;
        label.text(text);&lt;br /&gt;
    } else {&lt;br /&gt;
        target.text(text);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 사이드바 업데이트&lt;br /&gt;
function updateSidebar() {&lt;br /&gt;
    if (!window.LANG) {&lt;br /&gt;
        setTimeout(updateSidebar, 100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
&lt;br /&gt;
    var newsTitle = t.news || &#039;뉴스&#039;;&lt;br /&gt;
    var changelogTitle = t.changelog || &#039;체인지로그&#039;;&lt;br /&gt;
    var recentTitle = t.recentChanges || &#039;최근 변경&#039;;&lt;br /&gt;
    var languageTitle = t.language || &#039;언어&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-language&#039;).text(languageTitle);&lt;br /&gt;
    renderSidebarLanguageBox();&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-news&#039;).text(newsTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-recent-main&#039;).text(recentTitle);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-search a&#039;).text(t.search);&lt;br /&gt;
    $(&#039;#clbi-search-input&#039;).attr(&#039;placeholder&#039;, t.search + &#039;...&#039;);&lt;br /&gt;
    $(&#039;#clbi-title-recent a&#039;).text(recentTitle);&lt;br /&gt;
    $(&#039;#clbi-title-guide-label&#039;).text(t.guide);&lt;br /&gt;
    $(&#039;#clbi-guide-link&#039;).text(t.getStarted);&lt;br /&gt;
    $(&#039;#clbi-title-links-label&#039;).text(t.links);&lt;br /&gt;
&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-contribution&#039;, t.contribution);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-watchlist&#039;, t.watchlist);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-preferences&#039;, t.preferences);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-logout&#039;, t.logout);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-login&#039;, t.login);&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var specialPage = String(mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
$(&#039;#clbi-left-news-recent-title&#039;).text(&#039;RECENT CHANGES&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.clbi-user-btn&#039;).removeClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;Contributions&#039; ||&lt;br /&gt;
        specialPage === &#039;기여&#039; ||&lt;br /&gt;
        pageName.indexOf(&#039;특수:기여&#039;) === 0 ||&lt;br /&gt;
        pageName.indexOf(&#039;Special:Contributions&#039;) === 0&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-contribution&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (specialPage === &#039;Watchlist&#039;) {&lt;br /&gt;
        $(&#039;#clbi-btn-watchlist&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;설정&#039; ||&lt;br /&gt;
        pageName === &#039;특수:설정&#039; ||&lt;br /&gt;
        pageName === &#039;Special:설정&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-preferences&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.toggleBtn&#039;).each(function() {&lt;br /&gt;
        var btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        if (!$(&#039;#&#039; + btn.data(&#039;target&#039;)).hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            btn.text(t.expand);&lt;br /&gt;
        } else {&lt;br /&gt;
            btn.text(t.collapse);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    updateLeftSidebarNationsImage();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function canShowContentTools() {&lt;br /&gt;
    // 비로그인 사용자는 편집/역사/공유 버튼을 숨김&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // MediaWiki가 현재 문서를 편집 가능하지 않다고 판단하면 숨김&lt;br /&gt;
    var isEditable = mw.config.get(&#039;wgIsProbablyEditable&#039;);&lt;br /&gt;
    if (isEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var relevantEditable = mw.config.get(&#039;wgRelevantPageIsProbablyEditable&#039;);&lt;br /&gt;
    if (relevantEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkNodes(root) {&lt;br /&gt;
    var seen = [];&lt;br /&gt;
    var nodes = [];&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
&lt;br /&gt;
    $root.find(&#039;#catlinks, .catlinks&#039;).add($root.filter(&#039;#catlinks, .catlinks&#039;)).each(function () {&lt;br /&gt;
        if (seen.indexOf(this) !== -1) return;&lt;br /&gt;
        seen.push(this);&lt;br /&gt;
        nodes.push(this);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return nodes;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinksTarget(root) {&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
    var parserOutput = $root.find(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
    var main = $root.find(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.liberty-content-main&#039;)) {&lt;br /&gt;
        parserOutput = $(root).find(&#039;.mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.mw-parser-output&#039;)) {&lt;br /&gt;
        parserOutput = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (parserOutput.length) return parserOutput;&lt;br /&gt;
    if (main.length) return main;&lt;br /&gt;
&lt;br /&gt;
    if (!root) {&lt;br /&gt;
        parserOutput = $(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
        if (parserOutput.length) return parserOutput;&lt;br /&gt;
        if (main.length) return main;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return $();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_CATLINKS_FETCH_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function getCurrentPageTitleForCatlinks() {&lt;br /&gt;
    return String(&lt;br /&gt;
        mw.config.get(&#039;wgPageName&#039;) ||&lt;br /&gt;
        mw.config.get(&#039;wgRelevantPageName&#039;) ||&lt;br /&gt;
        &#039;&#039;&lt;br /&gt;
    ).trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function shouldFetchCatlinks() {&lt;br /&gt;
    var pageName = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) return false;&lt;br /&gt;
    if (specialPage) return false;&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearCatlinksInlineHiding(cat) {&lt;br /&gt;
    if (!cat || !cat.style) return;&lt;br /&gt;
&lt;br /&gt;
    cat.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-normal-catlinks, #mw-normal-catlinks, .mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        if (!this.style) return;&lt;br /&gt;
        this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function exposeHiddenCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        this.classList.remove(&#039;mw-hidden-cats-hidden&#039;);&lt;br /&gt;
        this.classList.add(&#039;mw-hidden-cats-user-shown&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (this.style) {&lt;br /&gt;
            this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkTextContent(cat) {&lt;br /&gt;
    var clone;&lt;br /&gt;
    var text;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    clone = cat.cloneNode(true);&lt;br /&gt;
    $(clone).find(&#039;script, style&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    text = String(clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*:\s*/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*$/i, &#039;&#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function hasRenderedCatlinkItems(cat) {&lt;br /&gt;
    var hasLink;&lt;br /&gt;
    var hasListText;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    hasLink = false;&lt;br /&gt;
    $(cat).find(&#039;a&#039;).each(function () {&lt;br /&gt;
        var text = String($(this).text() || &#039;&#039;).trim();&lt;br /&gt;
        var href = String(this.getAttribute(&#039;href&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        if (text || href) hasLink = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasLink) return true;&lt;br /&gt;
&lt;br /&gt;
    hasListText = false;&lt;br /&gt;
    $(cat).find(&#039;li&#039;).each(function () {&lt;br /&gt;
        if (String($(this).text() || &#039;&#039;).trim()) hasListText = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasListText) return true;&lt;br /&gt;
&lt;br /&gt;
    return !!getCatlinkTextContent(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCategoryTitle(rawTitle) {&lt;br /&gt;
    var title = String(rawTitle == null ? &#039;&#039; : rawTitle).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!title) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    title = title.replace(/_/g, &#039; &#039;);&lt;br /&gt;
&lt;br /&gt;
    if (/^(Category|분류):/i.test(title)) {&lt;br /&gt;
        return title;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;분류:&#039; + title;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeCategoryLinkTitle(rawTitle) {&lt;br /&gt;
    return String(rawTitle || &#039;&#039;)&lt;br /&gt;
        .replace(/^Category:/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^분류:/, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function dedupeCatlinkCategories(categories) {&lt;br /&gt;
    var seen = {};&lt;br /&gt;
    var result = [];&lt;br /&gt;
&lt;br /&gt;
    (categories || []).forEach(function (item) {&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var hidden = false;&lt;br /&gt;
&lt;br /&gt;
        if (typeof item === &#039;string&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item);&lt;br /&gt;
        } else if (item &amp;amp;&amp;amp; typeof item === &#039;object&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item.title || item.name || item.category || &#039;&#039;);&lt;br /&gt;
            hidden = item.hidden !== undefined || item.isHidden === true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
        if (seen[title]) return;&lt;br /&gt;
&lt;br /&gt;
        seen[title] = true;&lt;br /&gt;
        result.push({ title: title, hidden: hidden });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getConfigCatlinksCategories() {&lt;br /&gt;
    var normal = mw.config.get(&#039;wgCategories&#039;) || [];&lt;br /&gt;
    var hidden = mw.config.get(&#039;wgHiddenCategories&#039;) || [];&lt;br /&gt;
    var categories = [];&lt;br /&gt;
&lt;br /&gt;
    if (!Array.isArray(normal)) normal = [];&lt;br /&gt;
    if (!Array.isArray(hidden)) hidden = [];&lt;br /&gt;
&lt;br /&gt;
    normal.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: false });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    hidden.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: true });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return dedupeCatlinkCategories(categories);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksReady(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksEmpty(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksPending(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-fetching&#039;, &#039;1&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderFetchedCatlinks(cat, categories, pageTitle) {&lt;br /&gt;
    var container;&lt;br /&gt;
    var ul;&lt;br /&gt;
    var normalized;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    normalized = dedupeCatlinkCategories(categories);&lt;br /&gt;
&lt;br /&gt;
    if (!normalized.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cat.innerHTML = &#039;&#039;;&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-api-populated&#039;);&lt;br /&gt;
&lt;br /&gt;
    container = document.createElement(&#039;div&#039;);&lt;br /&gt;
    container.className = &#039;mw-normal-catlinks&#039;;&lt;br /&gt;
    container.appendChild(document.createTextNode(&#039;분류: &#039;));&lt;br /&gt;
&lt;br /&gt;
    ul = document.createElement(&#039;ul&#039;);&lt;br /&gt;
&lt;br /&gt;
    normalized.forEach(function (item) {&lt;br /&gt;
        var title = String(item &amp;amp;&amp;amp; item.title ? item.title : &#039;&#039;).trim();&lt;br /&gt;
        var li;&lt;br /&gt;
        var a;&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
&lt;br /&gt;
        li = document.createElement(&#039;li&#039;);&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = mw.util.getUrl(title);&lt;br /&gt;
        a.title = title;&lt;br /&gt;
        a.textContent = makeCategoryLinkTitle(title);&lt;br /&gt;
&lt;br /&gt;
        if (item.hidden) {&lt;br /&gt;
            li.className = &#039;clbi-hidden-category-item&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        li.appendChild(a);&lt;br /&gt;
        ul.appendChild(li);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (!ul.children.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.appendChild(ul);&lt;br /&gt;
    cat.appendChild(container);&lt;br /&gt;
    markCatlinksReady(cat, pageTitle);&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksForPage(pageTitle, callback) {&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (typeof pageTitle === &#039;function&#039;) {&lt;br /&gt;
        callback = pageTitle;&lt;br /&gt;
        pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    pageTitle = String(pageTitle || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!pageTitle || !shouldFetchCatlinks()) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;categories&#039;,&lt;br /&gt;
        titles: pageTitle,&lt;br /&gt;
        cllimit: &#039;max&#039;,&lt;br /&gt;
        clprop: &#039;hidden&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).done(function (data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages &amp;amp;&amp;amp; pages.length ? pages[0] : null;&lt;br /&gt;
        var categories = page &amp;amp;&amp;amp; page.categories ? page.categories : [];&lt;br /&gt;
&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback(categories || [], pageTitle);&lt;br /&gt;
    }).fail(function () {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finalizeEmptyCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    markCatlinksEmpty(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksIfNeeded(cat) {&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(cat, configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) {&lt;br /&gt;
        finalizeEmptyCatlinks(cat);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (cat.getAttribute(&#039;data-clbi-catlinks-fetching&#039;) === &#039;1&#039; &amp;amp;&amp;amp; cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) === pageTitle) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
    markCatlinksPending(cat, pageTitle);&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (!cat || !cat.isConnected) return;&lt;br /&gt;
        if (cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) !== requestedPage) return;&lt;br /&gt;
&lt;br /&gt;
        if (!renderFetchedCatlinks(cat, categories, requestedPage)) {&lt;br /&gt;
            finalizeEmptyCatlinks(cat);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCatlinksPanel(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksIfNeeded(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createCatlinksPanel(target, className) {&lt;br /&gt;
    var cat;&lt;br /&gt;
&lt;br /&gt;
    if (!target || !target.length) return null;&lt;br /&gt;
&lt;br /&gt;
    cat = document.createElement(&#039;div&#039;);&lt;br /&gt;
    cat.id = &#039;catlinks&#039;;&lt;br /&gt;
    cat.className = className || &#039;catlinks clbi-catlinks-created clbi-catlinks-pending&#039;;&lt;br /&gt;
    target.append(cat);&lt;br /&gt;
    return cat;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prepareSpaCatlinksBeforeInsert(root) {&lt;br /&gt;
    var nodes;&lt;br /&gt;
    var target;&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    nodes = getCatlinkNodes(root);&lt;br /&gt;
&lt;br /&gt;
    nodes.forEach(function (node) {&lt;br /&gt;
        node.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
        clearCatlinksInlineHiding(node);&lt;br /&gt;
        exposeHiddenCatlinks(node);&lt;br /&gt;
&lt;br /&gt;
        if (hasRenderedCatlinkItems(node)) {&lt;br /&gt;
            markCatlinksReady(node, pageTitle);&lt;br /&gt;
        } else if (configCategories.length) {&lt;br /&gt;
            renderFetchedCatlinks(node, configCategories, pageTitle);&lt;br /&gt;
        } else {&lt;br /&gt;
            markCatlinksEmpty(node);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (nodes.length || !configCategories.length) return;&lt;br /&gt;
&lt;br /&gt;
    target = getCatlinksTarget(root);&lt;br /&gt;
    if (!target.length) target = $(root);&lt;br /&gt;
&lt;br /&gt;
    renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveCatlinksToBottom() {&lt;br /&gt;
    var main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
    var target = getCatlinksTarget();&lt;br /&gt;
    var catlinks = getCatlinkNodes();&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!main.length || !target.length) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    catlinks.forEach(function (node) {&lt;br /&gt;
        var catNode = $(node);&lt;br /&gt;
&lt;br /&gt;
        if (node.parentNode !== target[0]) {&lt;br /&gt;
            catNode.appendTo(target);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        normalizeCatlinksPanel(node);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (catlinks.length) return;&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        var cat;&lt;br /&gt;
&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (getCatlinkNodes().length) return;&lt;br /&gt;
        if (!categories || !categories.length) return;&lt;br /&gt;
&lt;br /&gt;
        cat = createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;);&lt;br /&gt;
        renderFetchedCatlinks(cat, categories, requestedPage);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 대문 스타일&lt;br /&gt;
function initCategoryNavIfAvailable(root) {&lt;br /&gt;
    /*&lt;br /&gt;
    CategoryNav.js는 대문 카테고리 네비를 SVG로 생성한다.&lt;br /&gt;
&lt;br /&gt;
    Common.js가 SPA로 본문을 갈아끼운 뒤에는 MediaWiki 원래 페이지 로드와 달리&lt;br /&gt;
    CategoryNav.js의 초기 DOMContentLoaded만으로는 새 mount를 다시 잡지 못할 수 있다.&lt;br /&gt;
    CategoryNav.js 자체도 mw.hook(&#039;wikipage.content&#039;)를 듣지만, 로드 순서와 SPA 타이밍이&lt;br /&gt;
    엇갈릴 수 있으므로 Common.js 쪽에서도 존재 여부를 확인한 뒤 한 번 더 호출한다.&lt;br /&gt;
&lt;br /&gt;
    이 함수는 CategoryNav.js가 아직 로드되지 않았으면 아무 것도 하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    if (&lt;br /&gt;
        window.CLBI &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI.categoryNav &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI.categoryNav.init === &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        window.CLBI.categoryNav.init(root || document);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeLegacyMainPageHero() {&lt;br /&gt;
    /*&lt;br /&gt;
    기존 대문 전용 레거시 요소 정리&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    이전 대문 구조에서는 Common.js가 본문 바깥에 #clbi-main-logo를 직접 삽입하고,&lt;br /&gt;
    본문 안의 #clbi-main-crt-hero를 #clbi-main-crt-hero-wrap으로 감싸서&lt;br /&gt;
    .liberty-content-main 위쪽으로 재배치했다.&lt;br /&gt;
&lt;br /&gt;
    새 대문은 본문 내부의 .main-portal이 로고, 알림, 카테고리 네비, 이미지 피드,&lt;br /&gt;
    방명록, 상태 패널을 모두 담당한다. 따라서 Common.js가 별도 로고나 CRT 래퍼를&lt;br /&gt;
    삽입하면 새 로고/콘텐츠와 중복된다.&lt;br /&gt;
&lt;br /&gt;
    여기서는 JS가 만들던 바깥 로고와 CRT 래퍼를 제거하고, 예전 대문 원본이나&lt;br /&gt;
    캐시된 렌더 결과에 남아 있을 수 있는 #clbi-main-crt-hero도 제거한다.&lt;br /&gt;
    */&lt;br /&gt;
    $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function setNativePageTitleHiddenHard(hidden) {&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    document.querySelectorAll(selectors.join(&#039;,&#039;)).forEach(function(node) {&lt;br /&gt;
        if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
        if (hidden) {&lt;br /&gt;
            node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;visibility&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;min-height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;margin&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;padding&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;overflow&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
        } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;min-height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;margin&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;padding&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyDefaultPageTitleVisibility() {&lt;br /&gt;
    var hideTitle = true;&lt;br /&gt;
    var isSystemAssetPage = false;&lt;br /&gt;
&lt;br /&gt;
    if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isSystemAssetPage === &#039;function&#039;) {&lt;br /&gt;
        isSystemAssetPage = window.CLBI_PAGE_SHELL.isSystemAssetPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage) {&lt;br /&gt;
        hideTitle = true;&lt;br /&gt;
    } else if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isBackendOrSystemPage === &#039;function&#039;) {&lt;br /&gt;
        hideTitle = !window.CLBI_PAGE_SHELL.isBackendOrSystemPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;)&lt;br /&gt;
        .toggleClass(&#039;page-title-hidden&#039;, hideTitle)&lt;br /&gt;
        .toggleClass(&#039;page-title-visible&#039;, !hideTitle)&lt;br /&gt;
        .toggleClass(&#039;clbi-system-doc-page&#039;, isSystemAssetPage);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.renderSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.renderSystemDocIndicator();&lt;br /&gt;
    } else if (!isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.removeSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.removeSystemDocIndicator();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (hideTitle) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(false);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyMainPageStyle() {&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    if (specialPage === &#039;Preferences&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var namespaceNumber = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var isMainPage = (pageName === &#039;대문&#039;);&lt;br /&gt;
    var isUserProfilePage = (namespaceNumber === 2);&lt;br /&gt;
    var isScreenDoc = ($(&#039;.screen-header&#039;).length &amp;gt; 0);&lt;br /&gt;
    var hideTools = (isMainPage || isUserProfilePage || !canShowContentTools());&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, isUserProfilePage);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;clbi-main-page&#039;, isMainPage);&lt;br /&gt;
&lt;br /&gt;
    // 모든 문서에서 분류 바를 본문 컨테이너 아래로 이동&lt;br /&gt;
    moveCatlinksToBottom();&lt;br /&gt;
&lt;br /&gt;
    if (isMainPage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 새 대문은 .main-portal 본문 구조가 로고/히어로를 담당한다.&lt;br /&gt;
        // Common.js의 구식 바깥 로고/CRT 재배치 루틴은 사용하지 않는다.&lt;br /&gt;
        removeLegacyMainPageHero();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    } else if (isUserProfilePage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else if (isScreenDoc) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        if ($(&#039;#clbi-tools-box&#039;).length === 0 &amp;amp;&amp;amp; canShowContentTools()) {&lt;br /&gt;
            var $toolsBox = $(&#039;&amp;lt;div id=&amp;quot;clbi-tools-box&amp;quot; class=&amp;quot;clbi-left-box&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsTitle = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;관리&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsContent = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-content&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
            $toolsContent.append($(&#039;.content-tools .btn-group&#039;).clone(true));&lt;br /&gt;
            $toolsBox.append($toolsTitle).append($toolsContent);&lt;br /&gt;
            $(&#039;#clbi-left-sidebar&#039;).append($toolsBox);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!isUserProfilePage) {&lt;br /&gt;
        $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
        $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    applyDefaultPageTitleVisibility();&lt;br /&gt;
    updateSidebar();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 본문 기본 목차 제거&lt;br /&gt;
function removeNativeTocFromContent() {&lt;br /&gt;
    $(&#039;.liberty-content-main #toc, .liberty-content-main .toc&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 ID 가져오기&lt;br /&gt;
function getHeadingId(heading) {&lt;br /&gt;
    if (heading.id) {&lt;br /&gt;
        return heading.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline[id]&#039;);&lt;br /&gt;
    if (headline &amp;amp;&amp;amp; headline.id) {&lt;br /&gt;
        return headline.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 제목 텍스트 가져오기&lt;br /&gt;
function getHeadingText(heading) {&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline&#039;);&lt;br /&gt;
    var source = headline || heading;&lt;br /&gt;
    var clone = source.cloneNode(true);&lt;br /&gt;
&lt;br /&gt;
    $(clone).find(&#039;.mw-editsection, .mw-editsection-bracket, .mw-editsection-divider&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    return (clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: 긴 제목에 자동 스크롤 적용&lt;br /&gt;
function initTocTitleScroll(root) {&lt;br /&gt;
    var $items = root&lt;br /&gt;
        ? $(root).find(&#039;.toc-scroll-text&#039;)&lt;br /&gt;
        : $(&#039;#side-toc-box .toc-scroll-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    $items.each(function () {&lt;br /&gt;
        var $text = $(this);&lt;br /&gt;
        var $wrap = $text.closest(&#039;.toc-scroll-wrap&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$wrap.length) return;&lt;br /&gt;
&lt;br /&gt;
        var wrapW = Math.floor($wrap.width());&lt;br /&gt;
        var textW = Math.ceil(this.scrollWidth);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 레이아웃 계산이 끝나지 않았으면 이번 실행에서는 건드리지 않는다.&lt;br /&gt;
        if (!wrapW || !textW) return;&lt;br /&gt;
&lt;br /&gt;
        if (textW &amp;lt;= wrapW + 12) {&lt;br /&gt;
            // 왼쪽 목차: 칸을 넘지 않는 제목은 전체 텍스트를 그대로 보여준다.&lt;br /&gt;
            $wrap.removeClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
            if ($text.data(&#039;toc-scroll-enabled&#039;)) {&lt;br /&gt;
                $text.css({&lt;br /&gt;
                    animation: &#039;&#039;,&lt;br /&gt;
                    &#039;animation-delay&#039;: &#039;&#039;,&lt;br /&gt;
                    &#039;--scroll-dist&#039;: &#039;&#039;&lt;br /&gt;
                });&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-enabled&#039;);&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-key&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var scrollDist = &#039;-&#039; + (textW - wrapW + 10) + &#039;px&#039;;&lt;br /&gt;
        var duration = Math.max(7, textW / 38) * 1.25;&lt;br /&gt;
        var scrollKey = scrollDist + &#039;|&#039; + duration;&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목에는 오른쪽 페이드와 스크롤을 적용한다.&lt;br /&gt;
        $wrap.addClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 같은 값으로 이미 적용된 애니메이션은 다시 초기화하지 않는다.&lt;br /&gt;
        if ($text.data(&#039;toc-scroll-key&#039;) === scrollKey) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $text.data(&#039;toc-scroll-enabled&#039;, true);&lt;br /&gt;
        $text.data(&#039;toc-scroll-key&#039;, scrollKey);&lt;br /&gt;
&lt;br /&gt;
        $text.css({&lt;br /&gt;
            // 왼쪽 목차: 페이지 진입 직후에는 잠시 읽을 시간을 준 뒤 흐르게 한다.&lt;br /&gt;
            animation: &#039;toc-scroll-blink-reset &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
            &#039;animation-delay&#039;: &#039;1s&#039;,&lt;br /&gt;
            &#039;--scroll-dist&#039;: scrollDist&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 목차를 왼쪽 사이드바에 새로 생성&lt;br /&gt;
function moveTocToLeftSidebar() {&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: MediaWiki가 만든 원래 목차는 본문에서 제거한다.&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
&lt;br /&gt;
    var leftSidebar = document.getElementById(&#039;clbi-left-sidebar&#039;);&lt;br /&gt;
    if (!leftSidebar) return;&lt;br /&gt;
&lt;br /&gt;
    var content =&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;) ||&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!content) return;&lt;br /&gt;
&lt;br /&gt;
    var headings = Array.prototype.slice.call(&lt;br /&gt;
        content.querySelectorAll(&#039;h2, h3&#039;)&lt;br /&gt;
    ).filter(function (heading) {&lt;br /&gt;
        if (heading.closest(&#039;#toc, .toc, #side-toc-box&#039;)) return false;&lt;br /&gt;
&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
&lt;br /&gt;
        if (!id || !text) return false;&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var tocKey = headings.map(function (heading) {&lt;br /&gt;
        return getHeadingId(heading) + &#039;|&#039; + getHeadingText(heading);&lt;br /&gt;
    }).join(&#039;||&#039;);&lt;br /&gt;
&lt;br /&gt;
    var existingBox = document.getElementById(&#039;side-toc-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 같은 문서에서 같은 목차를 이미 만들었다면 다시 지우고 만들지 않는다.&lt;br /&gt;
    if (existingBox &amp;amp;&amp;amp; existingBox.getAttribute(&#039;data-toc-key&#039;) === tocKey) {&lt;br /&gt;
        initTocTitleScroll(existingBox);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (existingBox) {&lt;br /&gt;
        existingBox.remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!headings.length) return;&lt;br /&gt;
&lt;br /&gt;
    var tocBox = document.createElement(&#039;div&#039;);&lt;br /&gt;
    tocBox.className = &#039;clbi-left-box&#039;;&lt;br /&gt;
    tocBox.id = &#039;side-toc-box&#039;;&lt;br /&gt;
    tocBox.setAttribute(&#039;data-toc-key&#039;, tocKey);&lt;br /&gt;
&lt;br /&gt;
    var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
    title.className = &#039;clbi-left-title&#039;;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 박스 제목은 Lang.js의 현재 UI 언어를 따른다.&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
    var tocTitleText = (t &amp;amp;&amp;amp; t.toc) ? t.toc : &#039;목차&#039;;&lt;br /&gt;
&lt;br /&gt;
    title.textContent = tocTitleText;&lt;br /&gt;
&lt;br /&gt;
    var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
    body.className = &#039;clbi-left-content toc-sidebar-content&#039;;&lt;br /&gt;
&lt;br /&gt;
    var list = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    list.className = &#039;generated-toc&#039;;&lt;br /&gt;
&lt;br /&gt;
    headings.forEach(function (heading) {&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
        var level = heading.tagName.toLowerCase() === &#039;h3&#039; ? 3 : 2;&lt;br /&gt;
&lt;br /&gt;
        var item = document.createElement(&#039;li&#039;);&lt;br /&gt;
        item.className = &#039;toc-level-&#039; + level;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.setAttribute(&#039;href&#039;, &#039;#&#039; + id);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목 스크롤을 위해 텍스트를 별도 span으로 감싼다.&lt;br /&gt;
        var textWrap = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textWrap.className = &#039;toc-scroll-wrap&#039;;&lt;br /&gt;
&lt;br /&gt;
        var textSpan = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textSpan.className = &#039;toc-scroll-text&#039;;&lt;br /&gt;
        textSpan.textContent = text;&lt;br /&gt;
&lt;br /&gt;
        textWrap.appendChild(textSpan);&lt;br /&gt;
        link.appendChild(textWrap);&lt;br /&gt;
&lt;br /&gt;
        item.appendChild(link);&lt;br /&gt;
        list.appendChild(item);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    body.appendChild(list);&lt;br /&gt;
    tocBox.appendChild(title);&lt;br /&gt;
    tocBox.appendChild(body);&lt;br /&gt;
    leftSidebar.appendChild(tocBox);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: DOM 배치가 끝난 뒤 긴 제목 스크롤 여부를 계산한다.&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        initTocTitleScroll(tocBox);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            initTocTitleScroll(tocBox);&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 우측 광고판: 이미지/번역 캡션 목록&lt;br /&gt;
var RIGHT_BILLBOARD_ITEMS = [&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
        alt: &#039;PROOF TO THE WORLD / YOU ONCE PART OF IT&#039;,&lt;br /&gt;
        duration: 3000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;당신이 한때 이 세계의&#039;,&lt;br /&gt;
            &#039;&#039;,&lt;br /&gt;
            &#039;일부였다는 것을 증명하십시오&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-003.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    }&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItem(index) {&lt;br /&gt;
    var items = RIGHT_BILLBOARD_ITEMS;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        return {&lt;br /&gt;
            file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
            alt: &#039;&#039;,&lt;br /&gt;
            caption: []&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var normalized = index % items.length;&lt;br /&gt;
    if (normalized &amp;lt; 0) normalized += items.length;&lt;br /&gt;
&lt;br /&gt;
    return items[normalized];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardImageUrl(fileName) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/&#039; + encodeURIComponent(fileName || &#039;Side-visual-001.png&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardCaptionHtml(item) {&lt;br /&gt;
    var lines = item &amp;amp;&amp;amp; item.caption ? item.caption : [];&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    function escapeCaptionText(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/\&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    lines.forEach(function(line) {&lt;br /&gt;
        var text = String(line == null ? &#039;&#039; : line);&lt;br /&gt;
        var isGap = !text.trim();&lt;br /&gt;
        var className = &#039;right-billboard-caption-line&#039; + (isGap ? &#039; is-gap&#039; : &#039;&#039;);&lt;br /&gt;
        html += &#039;&amp;lt;span class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; + (isGap ? &#039;&amp;amp;nbsp;&#039; : escapeCaptionText(text)) + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setRightBillboardItem(index) {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    var item = getRightBillboardItem(index);&lt;br /&gt;
    var src = getRightBillboardImageUrl(item.file);&lt;br /&gt;
    var images = box.querySelectorAll(&#039;.right-billboard-image&#039;);&lt;br /&gt;
    var caption = box.querySelector(&#039;#right-billboard-caption&#039;);&lt;br /&gt;
    var emptySub = box.querySelector(&#039;.right-billboard-empty-sub&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, String(index));&lt;br /&gt;
    box.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(images, function(img) {&lt;br /&gt;
        img.style.display = &#039;&#039;;&lt;br /&gt;
        img.setAttribute(&#039;src&#039;, src);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, img.classList.contains(&#039;right-billboard-image-base&#039;) ? (item.alt || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (caption) {&lt;br /&gt;
        caption.innerHTML = getRightBillboardCaptionHtml(item);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (emptySub) {&lt;br /&gt;
        emptySub.textContent = item.file || &#039;Side-visual-001.png&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItemDuration(item) {&lt;br /&gt;
    var duration = item &amp;amp;&amp;amp; item.duration ? parseInt(item.duration, 10) : 3000;&lt;br /&gt;
&lt;br /&gt;
    if (Number.isNaN(duration) || duration &amp;lt; 500) {&lt;br /&gt;
        duration = 3000;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return duration;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initRightBillboardCarousel() {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-billboard-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    setRightBillboardItem(0);&lt;br /&gt;
&lt;br /&gt;
    if (!RIGHT_BILLBOARD_ITEMS || RIGHT_BILLBOARD_ITEMS.length &amp;lt;= 1) return;&lt;br /&gt;
&lt;br /&gt;
    function scheduleNext() {&lt;br /&gt;
        var current = parseInt(box.getAttribute(&#039;data-billboard-index&#039;) || &#039;0&#039;, 10);&lt;br /&gt;
        if (Number.isNaN(current)) current = 0;&lt;br /&gt;
&lt;br /&gt;
        var currentItem = getRightBillboardItem(current);&lt;br /&gt;
        var delay = getRightBillboardItemDuration(currentItem);&lt;br /&gt;
&lt;br /&gt;
        window.setTimeout(function() {&lt;br /&gt;
            if (!document.body.contains(box)) return;&lt;br /&gt;
&lt;br /&gt;
            if (!document.hidden) {&lt;br /&gt;
                setRightBillboardItem(current + 1);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            scheduleNext();&lt;br /&gt;
        }, delay);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scheduleNext();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function escapeRightBillboardAttr(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildRightBillboardBox() {&lt;br /&gt;
    var billboardInitial = getRightBillboardItem(0);&lt;br /&gt;
    var billboardSrc = getRightBillboardImageUrl(billboardInitial.file);&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box right-billboard-box left-billboard-box left-ad-box&amp;quot; data-billboard-index=&amp;quot;0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title right-billboard-title left-ad-title left-ad-title-iconless&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-ad&amp;quot; class=&amp;quot;left-ad-title-label&amp;quot;&amp;gt;Looking for a job?&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content left-ad-content-shell&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;right-billboard-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-recess&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;right-billboard-screen&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;right-billboard-image&amp;quot; class=&amp;quot;right-billboard-image right-billboard-image-base&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&#039; + escapeRightBillboardAttr(billboardInitial.alt || &#039;&#039;) + &#039;&amp;quot; onload=&amp;quot;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.remove(\&#039;is-empty\&#039;);}&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.add(\&#039;is-empty\&#039;);}&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-image-bloom&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-a&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-b&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-c&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-glitch&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-tear&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-empty&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-main&amp;quot;&amp;gt;SIGNAL EMPTY&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-sub&amp;quot;&amp;gt;&#039; + escapeRightBillboardAttr(billboardInitial.file || &#039;Side-visual-001.png&#039;) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;right-billboard-caption&amp;quot; class=&amp;quot;right-billboard-caption&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + getRightBillboardCaptionHtml(billboardInitial) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-bottom-finish left-ad-bottom-finish&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var GREAT_WALL_DATA_TITLE = &#039;프로젝트:The_Great_Wall/Data.json&#039;;&lt;br /&gt;
var GREAT_WALL_LIST_LIMIT = 0;&lt;br /&gt;
var greatWallState = {&lt;br /&gt;
    data: { entries: {} },&lt;br /&gt;
    loaded: false,&lt;br /&gt;
    loading: false,&lt;br /&gt;
    saving: false,&lt;br /&gt;
    selectedOwnEntry: false,&lt;br /&gt;
    statusText: &#039;&#039;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function normalizeGreatWallData(data) {&lt;br /&gt;
    var normalized = { entries: {} };&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    Object.keys(entries).forEach(function(key) {&lt;br /&gt;
        var item = entries[key];&lt;br /&gt;
        var user = item &amp;amp;&amp;amp; item.user ? String(item.user) : String(key || &#039;&#039;);&lt;br /&gt;
        var text = item &amp;amp;&amp;amp; item.text ? String(item.text) : &#039;&#039;;&lt;br /&gt;
        var timestamp = item &amp;amp;&amp;amp; item.timestamp ? String(item.timestamp) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!user || !text) return;&lt;br /&gt;
&lt;br /&gt;
        normalized.entries[user] = {&lt;br /&gt;
            user: user,&lt;br /&gt;
            text: text.slice(0, 140),&lt;br /&gt;
            timestamp: timestamp || new Date(0).toISOString()&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseGreatWallData(text) {&lt;br /&gt;
    var parsed;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;The Great Wall data parse failed:&#039;, err);&lt;br /&gt;
        parsed = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return normalizeGreatWallData(parsed);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function stringifyGreatWallData(data) {&lt;br /&gt;
    return JSON.stringify(normalizeGreatWallData(data), null, 2) + &#039;\n&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallRevisionText(page) {&lt;br /&gt;
    var rev;&lt;br /&gt;
    var slot;&lt;br /&gt;
&lt;br /&gt;
    if (!page || !page.revisions || !page.revisions.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    rev = page.revisions[0];&lt;br /&gt;
&lt;br /&gt;
    if (rev.slots &amp;amp;&amp;amp; rev.slots.main) {&lt;br /&gt;
        slot = rev.slots.main;&lt;br /&gt;
        return slot.content || slot[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return rev.content || rev[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchGreatWallData() {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
    return api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;revisions&#039;,&lt;br /&gt;
        titles: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
        rvprop: &#039;content|timestamp&#039;,&lt;br /&gt;
        rvslots: &#039;main&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages[0] || null;&lt;br /&gt;
&lt;br /&gt;
        if (!page || page.missing) {&lt;br /&gt;
            return { entries: {} };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parseGreatWallData(getGreatWallRevisionText(page));&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall load failed:&#039;, err);&lt;br /&gt;
        return { entries: {} };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallEntries(data) {&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    return Object.keys(entries).map(function(key) {&lt;br /&gt;
        return entries[key];&lt;br /&gt;
    }).filter(function(item) {&lt;br /&gt;
        return item &amp;amp;&amp;amp; item.user &amp;amp;&amp;amp; item.text;&lt;br /&gt;
    }).sort(function(a, b) {&lt;br /&gt;
        return String(a.timestamp || &#039;&#039;).localeCompare(String(b.timestamp || &#039;&#039;));&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallMessageTime(timestamp) {&lt;br /&gt;
    var date = timestamp ? new Date(timestamp) : null;&lt;br /&gt;
    var month;&lt;br /&gt;
    var day;&lt;br /&gt;
    var hour;&lt;br /&gt;
    var minute;&lt;br /&gt;
&lt;br /&gt;
    if (!date || isNaN(date.getTime())) return &#039;—&#039;;&lt;br /&gt;
&lt;br /&gt;
    month = String(date.getMonth() + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
    day = String(date.getDate()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    hour = String(date.getHours()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    minute = String(date.getMinutes()).padStart(2, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    return month + &#039;.&#039; + day + &#039; &#039; + hour + &#039;:&#039; + minute;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallAvatarSrc(user) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(String(user || &#039;&#039;)) + &#039;.png&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallEntryHtml(item, currentUser, selectedOwnEntry) {&lt;br /&gt;
    var isOwn = currentUser &amp;amp;&amp;amp; item.user === currentUser;&lt;br /&gt;
    var tag = isOwn ? &#039;button&#039; : &#039;div&#039;;&lt;br /&gt;
    var attrs = isOwn ? &#039; type=&amp;quot;button&amp;quot; data-great-wall-own-entry=&amp;quot;1&amp;quot; aria-label=&amp;quot;Edit your wall message&amp;quot;&#039; : &#039;&#039;;&lt;br /&gt;
    var className = &#039;great-wall-entry&#039; + (isOwn ? &#039; is-own&#039; : &#039;&#039;) + (isOwn &amp;amp;&amp;amp; selectedOwnEntry ? &#039; is-selected&#039; : &#039;&#039;);&lt;br /&gt;
    var avatarSrc = getGreatWallAvatarSrc(item.user);&lt;br /&gt;
    var messageTime = getGreatWallMessageTime(item.timestamp);&lt;br /&gt;
    var isoTime = item.timestamp || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;&#039; + tag + attrs + &#039; class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;img class=&amp;quot;great-wall-avatar&amp;quot; src=&amp;quot;&#039; + escapeClbiHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=&amp;amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;quot;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-bubble&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-entry-head&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-user&amp;quot;&amp;gt;@&#039; + escapeClbiHtml(item.user) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-time&amp;quot; title=&amp;quot;&#039; + escapeClbiHtml(isoTime) + &#039;&amp;quot;&amp;gt;&#039; + escapeClbiHtml(messageTime) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-text&amp;quot;&amp;gt;&#039; + escapeClbiHtml(item.text) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/&#039; + tag + &#039;&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var list = document.getElementById(&#039;great-wall-list&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
    var status = document.getElementById(&#039;great-wall-status&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var entries = getGreatWallEntries(greatWallState.data);&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var visibleEntries = [];&lt;br /&gt;
    var seen = {};&lt;br /&gt;
&lt;br /&gt;
    if (!box || !list) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.toggle(&#039;is-guest&#039;, !currentUser);&lt;br /&gt;
    box.classList.toggle(&#039;is-loading&#039;, !!greatWallState.loading);&lt;br /&gt;
    box.classList.toggle(&#039;is-saving&#039;, !!greatWallState.saving);&lt;br /&gt;
    box.classList.toggle(&#039;has-own-entry&#039;, !!ownEntry);&lt;br /&gt;
    box.classList.toggle(&#039;is-own-selected&#039;, !!greatWallState.selectedOwnEntry);&lt;br /&gt;
&lt;br /&gt;
    entries.forEach(function(item) {&lt;br /&gt;
        if (seen[item.user]) return;&lt;br /&gt;
        visibleEntries.push(item);&lt;br /&gt;
        seen[item.user] = true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.loading &amp;amp;&amp;amp; !greatWallState.loaded) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else if (!visibleEntries.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;NO MARKS&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else {&lt;br /&gt;
        list.innerHTML = visibleEntries.map(function(item) {&lt;br /&gt;
            return buildGreatWallEntryHtml(item, currentUser, greatWallState.selectedOwnEntry);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list &amp;amp;&amp;amp; list.scrollHeight &amp;gt; list.clientHeight) {&lt;br /&gt;
        list.scrollTop = list.scrollHeight;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (status) {&lt;br /&gt;
        // Status text is intentionally suppressed in the composer strip; the area is spacing-only UI.&lt;br /&gt;
        status.textContent = &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!input || !submit) return;&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        submit.disabled = false;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = &#039;Login required&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.saving || greatWallState.loading) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = greatWallState.saving ? &#039;SAVE&#039; : &#039;SYNC&#039;;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = greatWallState.saving ? &#039;Saving&#039; : &#039;Syncing&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.disabled = !ownEntry;&lt;br /&gt;
        deleteButton.title = ownEntry ? &#039;Delete your mark&#039; : &#039;No mark to delete&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        input.disabled = false;&lt;br /&gt;
        input.readOnly = true;&lt;br /&gt;
        input.setAttribute(&#039;aria-readonly&#039;, &#039;true&#039;);&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = &#039;UPDATE&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
    input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; greatWallState.selectedOwnEntry &amp;amp;&amp;amp; !input.value) {&lt;br /&gt;
        input.value = ownEntry.text || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    submit.disabled = false;&lt;br /&gt;
    submit.textContent = ownEntry ? &#039;UPDATE&#039; : &#039;POST&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function saveGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var text = input ? String(input.value || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!text) {&lt;br /&gt;
        greatWallState.statusText = &#039;EMPTY MARK&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (text.length &amp;gt; 140) {&lt;br /&gt;
        text = text.slice(0, 140);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SAVING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        data.entries[currentUser] = {&lt;br /&gt;
            user: currentUser,&lt;br /&gt;
            text: text,&lt;br /&gt;
            timestamp: new Date().toISOString()&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Update The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK UPDATED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall save failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;SAVE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function deleteGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!ownEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;NO MARK TO DELETE&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;DELETING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        if (data.entries &amp;amp;&amp;amp; data.entries[currentUser]) {&lt;br /&gt;
            delete data.entries[currentUser];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Delete The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK DELETED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall delete failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;DELETE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBoxWhenReady() {&lt;br /&gt;
    if (!document.getElementById(&#039;great-wall-sidebar&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    if (mw.Api) {&lt;br /&gt;
        initGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.loader &amp;amp;&amp;amp; mw.loader.using) {&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
            initGreatWallBox();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-great-wall-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-great-wall-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var ownButton = e.target.closest ? e.target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;]&#039;) : null;&lt;br /&gt;
        var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
        var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
&lt;br /&gt;
        if (!ownButton || !ownEntry) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = true;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
        if (input) {&lt;br /&gt;
            input.focus();&lt;br /&gt;
            input.setSelectionRange(input.value.length, input.value.length);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var target = e.target;&lt;br /&gt;
        var keepSelection;&lt;br /&gt;
&lt;br /&gt;
        if (!greatWallState.selectedOwnEntry || !target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
        keepSelection = target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;], .great-wall-editor, .great-wall-compose-sector&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (keepSelection) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        if (input) input.value = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (submit) {&lt;br /&gt;
        submit.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
                window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            saveGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            deleteGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (input) {&lt;br /&gt;
        input.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
            if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
                e.preventDefault();&lt;br /&gt;
                saveGreatWallEntry();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&#039;input&#039;, function() {&lt;br /&gt;
            if (input.value.length &amp;gt; 140) {&lt;br /&gt;
                input.value = input.value.slice(0, 140);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.loading = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SYNCING WALL&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        greatWallState.data = normalizeGreatWallData(data);&lt;br /&gt;
        greatWallState.loaded = true;&lt;br /&gt;
        greatWallState.loading = false;&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;great-wall-sidebar&amp;quot; class=&amp;quot;clbi-right-box great-wall-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title great-wall-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-great-wall&amp;quot;&amp;gt;The Great Wall&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content great-wall-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-list&amp;quot; class=&amp;quot;great-wall-list&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-compose-sector&amp;quot; aria-label=&amp;quot;The Great Wall editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;input id=&amp;quot;great-wall-input&amp;quot; class=&amp;quot;great-wall-input&amp;quot; type=&amp;quot;text&amp;quot; maxlength=&amp;quot;140&amp;quot; autocomplete=&amp;quot;off&amp;quot; placeholder=&amp;quot;담벼락&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-submit&amp;quot; class=&amp;quot;great-wall-submit&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;POST&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-delete&amp;quot; class=&amp;quot;great-wall-delete&amp;quot; type=&amp;quot;button&amp;quot; disabled&amp;gt;DEL&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-status&amp;quot; class=&amp;quot;great-wall-status&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeMainPortalGuestbookPreview() {&lt;br /&gt;
    var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    var guestbook = portal ? portal.querySelector(&#039;.guestbook-device&#039;) : null;&lt;br /&gt;
    var sideScreen;&lt;br /&gt;
    var grid;&lt;br /&gt;
&lt;br /&gt;
    if (!portal || !guestbook) return;&lt;br /&gt;
&lt;br /&gt;
    sideScreen = guestbook.closest ? guestbook.closest(&#039;.side-screen&#039;) : null;&lt;br /&gt;
    grid = sideScreen &amp;amp;&amp;amp; sideScreen.closest ? sideScreen.closest(&#039;.console-grid&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (guestbook.parentNode) {&lt;br /&gt;
        guestbook.parentNode.removeChild(guestbook);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sideScreen &amp;amp;&amp;amp; !(sideScreen.textContent || &#039;&#039;).trim() &amp;amp;&amp;amp; !sideScreen.querySelector(&#039;img,svg,video,canvas,form,input,button,a&#039;)) {&lt;br /&gt;
        if (sideScreen.parentNode) {&lt;br /&gt;
            sideScreen.parentNode.removeChild(sideScreen);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (grid) {&lt;br /&gt;
            grid.style.gridTemplateColumns = &#039;minmax(0,1fr)&#039;;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    portal.classList.add(&#039;is-great-wall-relocated&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildSiteInformationBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-right-box site-info-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title site-info-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span&amp;gt;정보&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content site-info-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;policy-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/개인정보처리방침&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;개인정보처리방침&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/면책_조항&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;면책 조항&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/라이선스&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;라이선스&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/크레딧&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;크레딧&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;social-strip&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://discord.gg/ctaeJ9d3Q5&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;DC&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.youtube.com/@nxdsxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;YT&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://x.com/nxd_sxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;X&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;WIP:&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 초기화 함수&lt;br /&gt;
function initSidebars() {&lt;br /&gt;
    var header = $(&#039;.liberty-content-header&#039;);&lt;br /&gt;
    var content = $(&#039;.liberty-content&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (header.length &amp;amp;&amp;amp; content.length) {&lt;br /&gt;
        header.prependTo(content);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-right-sidebar&#039;).length === 0) {&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        var isLoggedIn = username !== null;&lt;br /&gt;
        var avatarSrc = isLoggedIn&lt;br /&gt;
            ? &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + username + &#039;.png&#039;&lt;br /&gt;
            : &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&#039;;&lt;br /&gt;
&lt;br /&gt;
        var userBox;&lt;br /&gt;
&lt;br /&gt;
        if (isLoggedIn) {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a href=&amp;quot;/index.php/사용자:&#039; + username + &#039;&amp;quot; id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;&#039; + username + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-quick-actions&amp;quot; aria-label=&amp;quot;프로필 빠른 메뉴&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-inventory&amp;quot; aria-label=&amp;quot;인벤토리&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_PACKAGE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;인벤토리&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-achievements&amp;quot; aria-label=&amp;quot;업적&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_TROPHY + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;업적&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-notifications&amp;quot; aria-label=&amp;quot;알림&amp;quot;&amp;gt;&amp;lt;span id=&amp;quot;profile-quick-notification-icon&amp;quot; class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_BELL + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;알림&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:기여/&#039; + username + &#039;&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-contribution&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_TEXT + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;기여&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:주시문서목록&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-watchlist&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_EYE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;주시문서 목록&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:설정&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-preferences&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SETTINGS + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;설정&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그아웃&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn clbi-user-btn-logout&amp;quot; id=&amp;quot;clbi-btn-logout&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그아웃&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row profile-name-row-guest&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;Guest&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그인&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-login&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그인&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        var greatWallBox = &#039;&#039;;&lt;br /&gt;
        var siteInformationBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            greatWallBox = buildGreatWallBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;The Great Wall build failed:&#039;, err);&lt;br /&gt;
            greatWallBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            siteInformationBox = buildSiteInformationBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Site information build failed:&#039;, err);&lt;br /&gt;
            siteInformationBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        var sidebar = userBox + greatWallBox + siteInformationBox;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).append(&#039;&amp;lt;div id=&amp;quot;clbi-right-sidebar&amp;quot;&amp;gt;&#039; + sidebar + &#039;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        removeMainPortalGuestbookPreview();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    initGreatWallBoxWhenReady();&lt;br /&gt;
    removeMainPortalGuestbookPreview();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-left-sidebar&#039;).length === 0) {&lt;br /&gt;
var leftBillboardBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            leftBillboardBox = buildRightBillboardBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Left billboard build failed:&#039;, err);&lt;br /&gt;
            leftBillboardBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
var leftSidebar =&lt;br /&gt;
    &#039;&amp;lt;div id=&amp;quot;clbi-left-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-language&amp;quot;&amp;gt;언어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content sidebar-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selector&amp;quot; class=&amp;quot;sidebar-lang-selector sidebar-lang-dial&amp;quot; tabindex=&amp;quot;0&amp;quot; role=&amp;quot;group&amp;quot; aria-label=&amp;quot;언어 선택&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-dial-stage&amp;quot; class=&amp;quot;sidebar-lang-dial-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-fan&amp;quot; class=&amp;quot;sidebar-lang-fan&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selected-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-left&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-selected-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;한국어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-availability-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-right is-current&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-availability-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;CURRENT&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-sidebar-lang-apply&amp;quot; class=&amp;quot;sidebar-lang-apply&amp;quot; aria-label=&amp;quot;언어 적용&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;sidebar-lang-apply-mark&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;✓&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-news&amp;quot;&amp;gt;뉴스&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content clbi-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-title&amp;quot;&amp;gt;CHANGELOG&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-changelog-feed&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;a href=&amp;quot;/index.php/체인지로그&amp;quot; class=&amp;quot;news-post-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-post-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-post-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-main&amp;quot;&amp;gt;체인지로그&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-post-jump&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-divider&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-recent-title&amp;quot;&amp;gt;RECENT CHANGES&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-recent-feed&amp;quot; id=&amp;quot;clbi-left-recent-list&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;a class=&amp;quot;news-fill-image-slot&amp;quot; id=&amp;quot;clbi-left-news-fill-image&amp;quot; href=&amp;quot;/index.php/특수:최근바뀜&amp;quot; aria-label=&amp;quot;최근 바뀜으로 이동&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-fill-image-frame&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-fill-image&amp;quot; style=&amp;quot;--news-fill-image-url:url(\&#039;/index.php?title=특수:Redirect/file/Side-news-fill-001.png\&#039;);&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;img class=&amp;quot;news-fill-image-probe&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Side-news-fill-001.png&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.closest(\&#039;.news-fill-image-slot\&#039;).classList.add(\&#039;is-empty\&#039;);this.remove();&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        leftBillboardBox +&lt;br /&gt;
    &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).prepend(leftSidebar);&lt;br /&gt;
&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
        loadRecentChangesList(&#039;#clbi-left-recent-list&#039;, 10);&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
        updateLeftSidebarNationsImage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        initRightBillboardCarousel();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;Right billboard carousel failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
    applyMainPageStyle();&lt;br /&gt;
    initClbiCustomDocumentScrollbars();&lt;br /&gt;
    initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
        window.ProgressSystemWebUi.boot(&#039;initSidebars&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initNotifications();&lt;br /&gt;
            initProfile();&lt;br /&gt;
            moveTocToLeftSidebar();&lt;br /&gt;
        }, 300);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 800);&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 1500);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    loadLangScript(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initSidebars();&lt;br /&gt;
        }, 100);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click.profileQuickPlaceholder&#039;, &#039;#profile-quick-inventory, #profile-quick-achievements&#039;, function(e) {&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
function extractJsonArrayAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var depth = 0;&lt;br /&gt;
    var inString = false;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = String(text || &#039;&#039;).indexOf(&#039;[&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (inString) {&lt;br /&gt;
            if (escaped) {&lt;br /&gt;
                escaped = false;&lt;br /&gt;
            } else if (ch === &#039;\\&#039;) {&lt;br /&gt;
                escaped = true;&lt;br /&gt;
            } else if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
                inString = false;&lt;br /&gt;
            }&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            inString = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;[&#039;) depth += 1;&lt;br /&gt;
        if (ch === &#039;]&#039;) {&lt;br /&gt;
            depth -= 1;&lt;br /&gt;
            if (depth === 0) {&lt;br /&gt;
                try {&lt;br /&gt;
                    return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    return null;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function extractJsonStringAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var colon;&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    colon = text.indexOf(&#039;:&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (colon === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = text.indexOf(&#039;&amp;quot;&#039;, colon + 1);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start + 1; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (escaped) {&lt;br /&gt;
            escaped = false;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;\\&#039;) {&lt;br /&gt;
            escaped = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                return text.slice(start + 1, i);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function syncCatlinksConfigFromSpaDocument(doc) {&lt;br /&gt;
    var scripts = doc ? doc.querySelectorAll(&#039;script&#039;) : [];&lt;br /&gt;
    var categories = null;&lt;br /&gt;
    var hiddenCategories = null;&lt;br /&gt;
    var relevantPageName = null;&lt;br /&gt;
    var pageName = null;&lt;br /&gt;
    var i;&lt;br /&gt;
    var text;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; scripts.length; i += 1) {&lt;br /&gt;
        text = scripts[i].textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (categories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) categories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (hiddenCategories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgHiddenCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) hiddenCategories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (relevantPageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgRelevantPageName&#039;);&lt;br /&gt;
            if (value !== null) relevantPageName = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgPageName&#039;);&lt;br /&gt;
            if (value !== null) pageName = value;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.config.set(&#039;wgCategories&#039;, Array.isArray(categories) ? categories : []);&lt;br /&gt;
    mw.config.set(&#039;wgHiddenCategories&#039;, Array.isArray(hiddenCategories) ? hiddenCategories : []);&lt;br /&gt;
&lt;br /&gt;
    if (relevantPageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, relevantPageName);&lt;br /&gt;
    } else if (pageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    CLBI_CATLINKS_FETCH_TOKEN += 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// SPA 네비게이션&lt;br /&gt;
function shouldSkip(url) {&lt;br /&gt;
    return url.match(/action=edit|action=submit|action=history|action=delete|action=protect|action=purge|특수:로그인|특수:로그아웃|Special:UserLogin|Special:UserLogout|특수:사용자정보|특수:비밀번호바꾸기|uselang=/);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    if (window._spaInitialized) return;&lt;br /&gt;
    window._spaInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    function isInternal(url) {&lt;br /&gt;
        var a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = url;&lt;br /&gt;
        return a.hostname === window.location.hostname;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getCachedSpaPageHtml(url) {&lt;br /&gt;
        if (!window.EntryStore || typeof window.EntryStore.getTextSync !== &#039;function&#039;) return &#039;&#039;;&lt;br /&gt;
        return window.EntryStore.getTextSync(url) || window.EntryStore.getTextSync(String(url || &#039;&#039;).replace(/^https?:\/\/[^/]+/i, &#039;&#039;)) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchSpaPageHtml(url) {&lt;br /&gt;
        var cached = getCachedSpaPageHtml(url);&lt;br /&gt;
        if (cached) return Promise.resolve(cached);&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; }).then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDetachedEntryContent(newContent) {&lt;br /&gt;
        /*&lt;br /&gt;
        Initial boot prepares entry artifacts; SPA is only allowed to consume them.&lt;br /&gt;
        This hook runs while the fetched page is still detached, before the user sees it.&lt;br /&gt;
        It must stay synchronous or already-resolved: if a subsystem cannot prepare from&lt;br /&gt;
        EntryStore immediately, it should leave the old fallback path in place instead of&lt;br /&gt;
        opening BootGate during SPA.&lt;br /&gt;
        */&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.prepareContentForEntry === &#039;function&#039;) {&lt;br /&gt;
                window.NationsPanel.prepareContentForEntry(newContent);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;entry content preparation failed:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadPage(url) {&lt;br /&gt;
        invalidateProfileRender();&lt;br /&gt;
&lt;br /&gt;
        return fetchSpaPageHtml(url)&lt;br /&gt;
            .then(function(html) {&lt;br /&gt;
                var parser = new DOMParser();&lt;br /&gt;
                var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
                var scripts = doc.querySelectorAll(&#039;script&#039;);&lt;br /&gt;
                for (var i = 0; i &amp;lt; scripts.length; i++) {&lt;br /&gt;
                    var src = scripts[i].textContent;&lt;br /&gt;
&lt;br /&gt;
                    if (src.indexOf(&#039;wgNamespaceNumber&#039;) !== -1) {&lt;br /&gt;
                        var match = src.match(/&amp;quot;wgNamespaceNumber&amp;quot;:(-?\d+)/);&lt;br /&gt;
                        if (match) mw.config.set(&#039;wgNamespaceNumber&#039;, parseInt(match[1], 10));&lt;br /&gt;
&lt;br /&gt;
                        var matchTitle = src.match(/&amp;quot;wgTitle&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchTitle) mw.config.set(&#039;wgTitle&#039;, matchTitle[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchPage = src.match(/&amp;quot;wgPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchPage) mw.config.set(&#039;wgPageName&#039;, matchPage[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchArticle = src.match(/&amp;quot;wgArticleId&amp;quot;:(\d+)/);&lt;br /&gt;
                        if (matchArticle) {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, parseInt(matchArticle[1], 10));&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, 0);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchIsMainPage = src.match(/&amp;quot;wgIsMainPage&amp;quot;:(true|false)/);&lt;br /&gt;
                        if (matchIsMainPage) {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, matchIsMainPage[1] === &#039;true&#039;);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchSpecial = src.match(/&amp;quot;wgCanonicalSpecialPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchSpecial) {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, matchSpecial[1]);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                syncCatlinksConfigFromSpaDocument(doc);&lt;br /&gt;
&lt;br /&gt;
                var newContent = doc.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
                var newTitle = doc.querySelector(&#039;.mw-page-title-main&#039;);&lt;br /&gt;
                var newHead = doc.querySelector(&#039;title&#039;);&lt;br /&gt;
                var newHeader = doc.querySelector(&#039;.liberty-content-header&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (newContent) {&lt;br /&gt;
                    prepareDetachedEntryContent(newContent);&lt;br /&gt;
                    prepareSpaCatlinksBeforeInsert(newContent);&lt;br /&gt;
                    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
                    $(&#039;.liberty-content-main&#039;).html(newContent.innerHTML);&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    try {&lt;br /&gt;
                        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.renderPrepared === &#039;function&#039;) window.Decorations.renderPrepared();&lt;br /&gt;
                        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.renderPrepared === &#039;function&#039;) window.CLBI_DECORATIONS.renderPrepared();&lt;br /&gt;
                    } catch (err) {}&lt;br /&gt;
                    $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newTitle) {&lt;br /&gt;
                    $(&#039;.mw-page-title-main&#039;).html(newTitle.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHead) {&lt;br /&gt;
                    document.title = newHead.textContent;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHeader) {&lt;br /&gt;
                    $(&#039;.liberty-content-header&#039;).html(newHeader.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
                window.scrollTo(0, 0);&lt;br /&gt;
                mw.hook(&#039;wikipage.content&#039;).fire($(&#039;.liberty-content-main&#039;));&lt;br /&gt;
                applyMainPageStyle();&lt;br /&gt;
                initClbiCustomDocumentScrollbars();&lt;br /&gt;
                initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
                if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.handleSpaPageView === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.handleSpaPageView();&lt;br /&gt;
                } else if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.boot(&#039;spa&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 100);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 500);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 1200);&lt;br /&gt;
&lt;br /&gt;
                mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
                    initProfile();&lt;br /&gt;
                    moveTocToLeftSidebar();&lt;br /&gt;
                });&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function (err) {&lt;br /&gt;
                console.error(&#039;SPA page load failed:&#039;, err);&lt;br /&gt;
                $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
// 목차 링크는 전용 처리&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;#side-toc-box a, #toc a, .toc a&#039;, function(e) {&lt;br /&gt;
    var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
    if (!href || href.charAt(0) !== &#039;#&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var rawId = href.slice(1);&lt;br /&gt;
    if (!rawId) return;&lt;br /&gt;
&lt;br /&gt;
    var decodedId = rawId;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        decodedId = decodeURIComponent(rawId);&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        decodedId = rawId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var target = document.getElementById(decodedId);&lt;br /&gt;
&lt;br /&gt;
    if (!target &amp;amp;&amp;amp; window.CSS &amp;amp;&amp;amp; CSS.escape) {&lt;br /&gt;
        target = document.querySelector(&#039;#&#039; + CSS.escape(decodedId));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!target) return;&lt;br /&gt;
&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
    var scrollTarget = target.closest(&#039;h2, h3&#039;) || target;&lt;br /&gt;
&lt;br /&gt;
    scrollTarget.scrollIntoView({&lt;br /&gt;
        behavior: &#039;auto&#039;,&lt;br /&gt;
        block: &#039;start&#039;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    history.replaceState(null, &#039;&#039;, &#039;#&#039; + rawId);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
    /* 길라잡이는 문서 링크가 아니라 대문 본문 화면 탭이다. */&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;, function(e) {&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.toggle === &#039;function&#039;) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopImmediatePropagation();&lt;br /&gt;
            window.BottomGuideNav.toggle();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;a&#039;, function(e) {&lt;br /&gt;
        // 휠 클릭, 새 탭 열기, 보조키 이동은 브라우저 기본 동작을 유지한다.&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        /* 길라잡이 화면 탭은 위 전용 처리 외에는 SPA 문서 이동 대상으로 삼지 않는다. */&lt;br /&gt;
        if ($(this).is(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
        if (!href) return;&lt;br /&gt;
&lt;br /&gt;
        // 목차 링크는 별도 핸들러에서 처리&lt;br /&gt;
        if ($(this).closest(&#039;#side-toc-box, #toc, .toc&#039;).length) return;&lt;br /&gt;
&lt;br /&gt;
        // 단순 해시 링크는 SPA 가로채기 제외&lt;br /&gt;
        if (href.startsWith(&#039;#&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.href = href;&lt;br /&gt;
&lt;br /&gt;
        var samePath = decodeURIComponent(link.pathname) === decodeURIComponent(window.location.pathname);&lt;br /&gt;
        var sameSearch = (link.search || &#039;&#039;) === (window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; samePath &amp;amp;&amp;amp; sameSearch) return;&lt;br /&gt;
&lt;br /&gt;
        var currentBase = window.location.href.split(&#039;#&#039;)[0];&lt;br /&gt;
        var targetBase = link.href.split(&#039;#&#039;)[0];&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; currentBase === targetBase) return;&lt;br /&gt;
&lt;br /&gt;
        if (!isInternal(href)) return;&lt;br /&gt;
        if (shouldSkip(href)) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        playStaticSound();&lt;br /&gt;
        /*&lt;br /&gt;
        SPA must remain a consumer phase.  If the target page HTML was prepared by&lt;br /&gt;
        the first-load boot pack, do not show the legacy page-loading veil.  The&lt;br /&gt;
        route will consume cached HTML and detached entry artifacts before insertion.&lt;br /&gt;
        */&lt;br /&gt;
        if (getCachedSpaPageHtml(href)) {&lt;br /&gt;
            $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;body&#039;).addClass(&#039;page-loading&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        history.pushState(null, &#039;&#039;, href);&lt;br /&gt;
        loadPage(href);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;popstate&#039;, function() {&lt;br /&gt;
        loadPage(window.location.href);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ========== CLBI Custom Document Scrollbar ========== */&lt;br /&gt;
function isGeneralDocumentView() {&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    if (!body) return false;&lt;br /&gt;
&lt;br /&gt;
    return body.classList.contains(&#039;action-view&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-main-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-system-doc-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;backend-system-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiDocumentScrollTargets() {&lt;br /&gt;
    if (!isGeneralDocumentView()) return [];&lt;br /&gt;
&lt;br /&gt;
    return Array.prototype.slice.call(document.querySelectorAll(&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; #mw-content-text .mw-parser-output, &#039; +&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; .mw-body-content .mw-parser-output&#039;&lt;br /&gt;
    )).filter(function (el, index, list) {&lt;br /&gt;
        return el &amp;amp;&amp;amp; list.indexOf(el) === index;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiOuterWellForScroll(scrollEl) {&lt;br /&gt;
    var main = scrollEl ? scrollEl.closest(&#039;.liberty-content-main&#039;) : null;&lt;br /&gt;
    var children;&lt;br /&gt;
    var i;&lt;br /&gt;
    var child;&lt;br /&gt;
&lt;br /&gt;
    if (!main) return null;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(main.children || []);&lt;br /&gt;
    for (i = 0; i &amp;lt; children.length; i += 1) {&lt;br /&gt;
        child = children[i];&lt;br /&gt;
        if (&lt;br /&gt;
            child &amp;amp;&amp;amp;&lt;br /&gt;
            (child.id === &#039;mw-content-text&#039; || child.classList.contains(&#039;mw-body-content&#039;)) &amp;amp;&amp;amp;&lt;br /&gt;
            child.contains(scrollEl)&lt;br /&gt;
        ) {&lt;br /&gt;
            return child;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return scrollEl.parentElement || null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiCustomScrollbar(well, scrollEl) {&lt;br /&gt;
    var bar = well.querySelector(&#039;:scope &amp;gt; .clbi-custom-scrollbar&#039;);&lt;br /&gt;
    var up;&lt;br /&gt;
    var track;&lt;br /&gt;
    var thumb;&lt;br /&gt;
    var down;&lt;br /&gt;
&lt;br /&gt;
    if (!bar) {&lt;br /&gt;
        bar = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bar.className = &#039;clbi-custom-scrollbar&#039;;&lt;br /&gt;
        bar.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        bar.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-up&amp;quot; data-scroll-arrow=&amp;quot;up&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-track&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;clbi-custom-scroll-thumb&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-down&amp;quot; data-scroll-arrow=&amp;quot;down&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        well.appendChild(bar);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.__clbiScrollTarget = scrollEl;&lt;br /&gt;
    up = bar.querySelector(&#039;.clbi-custom-scroll-arrow-up&#039;);&lt;br /&gt;
    track = bar.querySelector(&#039;.clbi-custom-scroll-track&#039;);&lt;br /&gt;
    thumb = bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;);&lt;br /&gt;
    down = bar.querySelector(&#039;.clbi-custom-scroll-arrow-down&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (up &amp;amp;&amp;amp; !up.__clbiBound) {&lt;br /&gt;
        up.__clbiBound = true;&lt;br /&gt;
        up.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop -= 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (down &amp;amp;&amp;amp; !down.__clbiBound) {&lt;br /&gt;
        down.__clbiBound = true;&lt;br /&gt;
        down.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop += 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (track &amp;amp;&amp;amp; !track.__clbiBound) {&lt;br /&gt;
        track.__clbiBound = true;&lt;br /&gt;
        track.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var rect;&lt;br /&gt;
            var thumbRect;&lt;br /&gt;
            var target;&lt;br /&gt;
            var direction;&lt;br /&gt;
&lt;br /&gt;
            if (e.target === thumb) return;&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            target = bar.__clbiScrollTarget;&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            rect = track.getBoundingClientRect();&lt;br /&gt;
            thumbRect = thumb.getBoundingClientRect();&lt;br /&gt;
            direction = e.clientY &amp;lt; thumbRect.top ? -1 : 1;&lt;br /&gt;
            target.scrollTop += direction * Math.max(60, Math.floor(target.clientHeight * 0.82));&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (thumb &amp;amp;&amp;amp; !thumb.__clbiBound) {&lt;br /&gt;
        thumb.__clbiBound = true;&lt;br /&gt;
        thumb.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var target = bar.__clbiScrollTarget;&lt;br /&gt;
            var startY;&lt;br /&gt;
            var startScroll;&lt;br /&gt;
            var maxScroll;&lt;br /&gt;
            var maxThumbTop;&lt;br /&gt;
            var trackHeight;&lt;br /&gt;
            var thumbHeight;&lt;br /&gt;
&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            startY = e.clientY;&lt;br /&gt;
            startScroll = target.scrollTop;&lt;br /&gt;
            maxScroll = Math.max(1, target.scrollHeight - target.clientHeight);&lt;br /&gt;
            trackHeight = track ? track.clientHeight : 0;&lt;br /&gt;
            thumbHeight = thumb.offsetHeight || 0;&lt;br /&gt;
            maxThumbTop = Math.max(1, trackHeight - thumbHeight);&lt;br /&gt;
&lt;br /&gt;
            bar.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
            function onMove(moveEvent) {&lt;br /&gt;
                var dy = moveEvent.clientY - startY;&lt;br /&gt;
                target.scrollTop = startScroll + (dy / maxThumbTop) * maxScroll;&lt;br /&gt;
                updateClbiCustomScrollbar(bar);&lt;br /&gt;
                moveEvent.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function onUp() {&lt;br /&gt;
                bar.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                document.removeEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
                document.removeEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            document.addEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
            document.addEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!scrollEl.__clbiCustomScrollbarBound) {&lt;br /&gt;
        scrollEl.__clbiCustomScrollbarBound = true;&lt;br /&gt;
        scrollEl.addEventListener(&#039;scroll&#039;, function () {&lt;br /&gt;
            if (scrollEl.__clbiCustomScrollbar) {&lt;br /&gt;
                updateClbiCustomScrollbar(scrollEl.__clbiCustomScrollbar);&lt;br /&gt;
            }&lt;br /&gt;
        }, { passive: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scrollEl.__clbiCustomScrollbar = bar;&lt;br /&gt;
    updateClbiCustomScrollbar(bar);&lt;br /&gt;
&lt;br /&gt;
    return bar;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiCustomScrollbar(bar) {&lt;br /&gt;
    var scrollEl = bar &amp;amp;&amp;amp; bar.__clbiScrollTarget;&lt;br /&gt;
    var track = bar ? bar.querySelector(&#039;.clbi-custom-scroll-track&#039;) : null;&lt;br /&gt;
    var thumb = bar ? bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;) : null;&lt;br /&gt;
    var maxScroll;&lt;br /&gt;
    var trackHeight;&lt;br /&gt;
    var thumbHeight;&lt;br /&gt;
    var maxTop;&lt;br /&gt;
    var top;&lt;br /&gt;
&lt;br /&gt;
    if (!bar || !scrollEl || !track || !thumb) return;&lt;br /&gt;
&lt;br /&gt;
    maxScroll = scrollEl.scrollHeight - scrollEl.clientHeight;&lt;br /&gt;
    if (maxScroll &amp;lt;= 1) {&lt;br /&gt;
        bar.classList.add(&#039;is-hidden&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.classList.remove(&#039;is-hidden&#039;);&lt;br /&gt;
&lt;br /&gt;
    trackHeight = Math.max(1, track.clientHeight || 1);&lt;br /&gt;
    thumbHeight = Math.max(12, Math.floor((scrollEl.clientHeight / Math.max(scrollEl.scrollHeight, 1)) * trackHeight));&lt;br /&gt;
    thumbHeight = Math.min(trackHeight, thumbHeight);&lt;br /&gt;
    maxTop = Math.max(0, trackHeight - thumbHeight);&lt;br /&gt;
    top = maxScroll &amp;gt; 0 ? Math.round((scrollEl.scrollTop / maxScroll) * maxTop) : 0;&lt;br /&gt;
&lt;br /&gt;
    thumb.style.height = thumbHeight + &#039;px&#039;;&lt;br /&gt;
    thumb.style.transform = &#039;translateY(&#039; + top + &#039;px)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiCustomDocumentScrollbars() {&lt;br /&gt;
    var existing = Array.prototype.slice.call(document.querySelectorAll(&#039;.clbi-custom-scrollbar&#039;));&lt;br /&gt;
    var targets = getClbiDocumentScrollTargets();&lt;br /&gt;
    var liveBars = [];&lt;br /&gt;
&lt;br /&gt;
    if (!targets.length) {&lt;br /&gt;
        existing.forEach(function (bar) { bar.remove(); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    targets.forEach(function (scrollEl) {&lt;br /&gt;
        var well = getClbiOuterWellForScroll(scrollEl);&lt;br /&gt;
        var bar;&lt;br /&gt;
&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
        bar = buildClbiCustomScrollbar(well, scrollEl);&lt;br /&gt;
        liveBars.push(bar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    existing.forEach(function (bar) {&lt;br /&gt;
        if (liveBars.indexOf(bar) === -1) bar.remove();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (!window.__clbiCustomScrollbarResizeBound) {&lt;br /&gt;
    window.__clbiCustomScrollbarResizeBound = true;&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        setTimeout(initClbiCustomDocumentScrollbars, 60);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시간 계산 함수&lt;br /&gt;
function timeAgo(timestamp) {&lt;br /&gt;
    var now = new Date();&lt;br /&gt;
    var date = new Date(timestamp);&lt;br /&gt;
    var diff = Math.floor((now - date) / 1000);&lt;br /&gt;
&lt;br /&gt;
    if (diff &amp;lt; 60) return diff + &#039;초 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 3600) return Math.floor(diff / 60) + &#039;분 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 86400) return Math.floor(diff / 3600) + &#039;시간 전&#039;;&lt;br /&gt;
    return Math.floor(diff / 86400) + &#039;일 전&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
function getFoldTexts() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return (window.LANG &amp;amp;&amp;amp; window.LANG[lang])&lt;br /&gt;
        ? window.LANG[lang]&lt;br /&gt;
        : (window.LANG ? window.LANG.ko : { expand: &#039;펼치기&#039;, collapse: &#039;접기&#039; });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function refreshOpenAncestors($start) {&lt;br /&gt;
    $start.parents(&#039;[id^=&amp;quot;collapsible&amp;quot;]&#039;).each(function () {&lt;br /&gt;
        var $parent = $(this);&lt;br /&gt;
        if (!$parent.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        // 이미 fully open 상태면 굳이 다시 잠그지 않음&lt;br /&gt;
        if ($parent.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $parent.css(&#039;max-height&#039;, this.scrollHeight + &#039;px&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindInnerResizeUpdates($target) {&lt;br /&gt;
    // 이미지 늦게 로드될 때 높이 갱신&lt;br /&gt;
    $target.find(&#039;img&#039;).off(&#039;.foldimg&#039;).on(&#039;load.foldimg&#039;, function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            if ($target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
                $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function openFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;opening&#039;);&lt;br /&gt;
    $target.addClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 열린 뒤 자연 확장 가능하게 만들기 위해 먼저 px로 열기&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.collapse);&lt;br /&gt;
&lt;br /&gt;
    bindInnerResizeUpdates($target);&lt;br /&gt;
&lt;br /&gt;
    // 바깥 펼접 즉시 갱신&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    // 전환 끝나면 none으로 풀어서 중첩 펼접/동적 내용 증가를 자연스럽게 허용&lt;br /&gt;
    $target.off(&#039;transitionend.foldopen&#039;).on(&#039;transitionend.foldopen&#039;, function (e) {&lt;br /&gt;
        if (e.target !== this) return;&lt;br /&gt;
        if (!$target.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        $target.css(&#039;max-height&#039;, &#039;none&#039;);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;open&#039;);&lt;br /&gt;
&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // 늦게 렌더되는 콘텐츠 대응&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 80);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 220);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function closeFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    // none 상태에서 닫으면 transition이 안 되므로 실제 높이로 고정&lt;br /&gt;
    if ($target.css(&#039;max-height&#039;) === &#039;none&#039; || $target.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;closing&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target.removeClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.expand);&lt;br /&gt;
&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;closed&#039;);&lt;br /&gt;
    }, 250);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function () {&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiToggle&#039;, &#039;.toggleBtn&#039;, function () {&lt;br /&gt;
            var $btn = $(this);&lt;br /&gt;
            var targetId = $btn.data(&#039;target&#039;);&lt;br /&gt;
            var $target = $(&#039;#&#039; + targetId);&lt;br /&gt;
            if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
            var scrollY = window.scrollY;&lt;br /&gt;
&lt;br /&gt;
            if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
                closeFold($target, $btn);&lt;br /&gt;
            } else {&lt;br /&gt;
                openFold($target, $btn);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            window.scrollTo(0, scrollY);&lt;br /&gt;
        });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// ========== 프로필 시스템 ==========&lt;br /&gt;
function initProfile() {&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var token = ++PROFILE_RENDER_TOKEN;&lt;br /&gt;
    var ns = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var title = mw.config.get(&#039;wgTitle&#039;);&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    var isProfileSettings = specialPage === &#039;사용자정보&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, ns === 2);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-settings-page&#039;, isProfileSettings);&lt;br /&gt;
&lt;br /&gt;
    if (ns === 2) {&lt;br /&gt;
        var profileUser = title.split(&#039;/&#039;)[0];&lt;br /&gt;
        renderProfile(profileUser, token);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isProfileSettings) {&lt;br /&gt;
        initUserProfilePage();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderProfile(username, token) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        list: &#039;users&#039;,&lt;br /&gt;
        ususers: username,&lt;br /&gt;
        usprop: &#039;editcount&#039;&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        if (token !== PROFILE_RENDER_TOKEN) return;&lt;br /&gt;
        if (mw.config.get(&#039;wgNamespaceNumber&#039;) !== 2) return;&lt;br /&gt;
&lt;br /&gt;
        var currentTitle = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).split(&#039;/&#039;)[0];&lt;br /&gt;
        if (currentTitle !== username) return;&lt;br /&gt;
&lt;br /&gt;
        var user = data.query.users[0];&lt;br /&gt;
        var contentEl = document.getElementById(&#039;mw-content-text&#039;);&lt;br /&gt;
        if (!contentEl) return;&lt;br /&gt;
&lt;br /&gt;
        var pageContent = contentEl.querySelector(&#039;.mw-parser-output&#039;) || contentEl;&lt;br /&gt;
        injectProfileCard(username, user, pageContent);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function injectProfileCard(username, userData, container) {&lt;br /&gt;
    var isOwnPage = mw.config.get(&#039;wgUserName&#039;) === username;&lt;br /&gt;
    var editCount = (userData &amp;amp;&amp;amp; userData.editcount) ? userData.editcount : 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.classList.add(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var safeUsername = escapeHtml(username);&lt;br /&gt;
    var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(username) + &#039;.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var fallbackSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var editBtn = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;a href=&amp;quot;/index.php/특수:사용자정보&amp;quot; class=&amp;quot;profile-edit-btn&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-label&amp;quot;&amp;gt;프로필 수정&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-arrow&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var progressHtml = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;div class=&amp;quot;profile-page-progress is-syncing&amp;quot; data-profile-progress&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;LEVEL RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-page-progress-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-level&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;profile-page-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-sub&amp;quot;&amp;gt;SYNCING&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-meta&amp;quot;&amp;gt;TODAY — · DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var card = document.createElement(&#039;div&#039;);&lt;br /&gt;
    card.className = &#039;profile-card profile-page-console&#039;;&lt;br /&gt;
    card.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-titlebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;USER PROFILE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;OFFICIAL ARCHIVE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-identity-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-avatar-bay&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;&#039; + fallbackSrc + &#039;\&#039;;&amp;quot; alt=&amp;quot;&#039; + safeUsername + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-info-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-nameplate&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;h2 class=&amp;quot;profile-username&amp;quot;&amp;gt;&#039; + safeUsername + &#039;&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
                        editBtn +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-name&amp;quot; data-field=&amp;quot;name&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-role&amp;quot; data-field=&amp;quot;role&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-discord&amp;quot; data-field=&amp;quot;discord&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-lower-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-bio-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;BIOGRAPHY&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-bio&amp;quot; data-field=&amp;quot;bio&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-stats-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-stats&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        progressHtml +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-stat-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot;&amp;gt;&#039; + editCount + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;수정 횟수&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot; data-contrib-pages-stat&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot; data-contrib-pages-value&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;기여 문서&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-info-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;TIME&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-time&amp;gt;UTC --:--&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;LANGUAGE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-language&amp;gt;—&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    container.insertBefore(card, container.firstChild);&lt;br /&gt;
    loadProfileFields(username, card);&lt;br /&gt;
    loadProfileContributionPages(username, card);&lt;br /&gt;
    updateProfilePageEnvironment(card, null);&lt;br /&gt;
&lt;br /&gt;
    if (isOwnPage) {&lt;br /&gt;
        loadProfileProgressForUserPage(card);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getProfileLanguageLabel() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || (lang ? lang.toUpperCase() : &#039;—&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfilePageEnvironment(card, summary) {&lt;br /&gt;
    if (!card) return;&lt;br /&gt;
&lt;br /&gt;
    var timezone = summary &amp;amp;&amp;amp; summary.timezone ? summary.timezone : &#039;UTC&#039;;&lt;br /&gt;
    var timeEl = card.querySelector(&#039;[data-profile-time]&#039;);&lt;br /&gt;
    var langEl = card.querySelector(&#039;[data-profile-language]&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (timeEl) {&lt;br /&gt;
        try {&lt;br /&gt;
            timeEl.textContent = timezone + &#039; &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: timezone&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            timeEl.textContent = &#039;UTC &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: &#039;UTC&#039;&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (langEl) {&lt;br /&gt;
        langEl.textContent = getProfileLanguageLabel();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileContributionPages(username, card) {&lt;br /&gt;
    if (!username || !card) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var valueEl = card.querySelector(&#039;[data-contrib-pages-value]&#039;);&lt;br /&gt;
    if (valueEl) valueEl.textContent = &#039;SYNC&#039;;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        var pages = Object.create(null);&lt;br /&gt;
        var cont = {};&lt;br /&gt;
        var guard = 0;&lt;br /&gt;
&lt;br /&gt;
        function requestNext() {&lt;br /&gt;
            guard++;&lt;br /&gt;
&lt;br /&gt;
            var params = Object.assign({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                list: &#039;usercontribs&#039;,&lt;br /&gt;
                ucuser: username,&lt;br /&gt;
                ucnamespace: 0,&lt;br /&gt;
                ucprop: &#039;title&#039;,&lt;br /&gt;
                uclimit: &#039;max&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }, cont);&lt;br /&gt;
&lt;br /&gt;
            return api.get(params).then(function (data) {&lt;br /&gt;
                var rows = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.usercontribs ? data.query.usercontribs : [];&lt;br /&gt;
&lt;br /&gt;
                rows.forEach(function (row) {&lt;br /&gt;
                    if (row &amp;amp;&amp;amp; row.title) pages[row.title] = true;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                if (data &amp;amp;&amp;amp; data.continue &amp;amp;&amp;amp; data.continue.uccontinue &amp;amp;&amp;amp; guard &amp;lt; 40) {&lt;br /&gt;
                    cont = data.continue;&lt;br /&gt;
                    return requestNext();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (valueEl) valueEl.textContent = Object.keys(pages).length;&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestNext().fail(function () {&lt;br /&gt;
            if (valueEl) valueEl.textContent = &#039;—&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileProgressForUserPage(card) {&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;progress_summary&#039;,&lt;br /&gt;
            format: &#039;json&#039;,&lt;br /&gt;
            formatversion: 2&lt;br /&gt;
        }).then(function (data) {&lt;br /&gt;
            var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
            if (!payload || !payload.available || !payload.summary) return;&lt;br /&gt;
            updateUserPageProgress(card, payload.summary);&lt;br /&gt;
            updateProfilePageEnvironment(card, payload.summary);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateUserPageProgress(card, summary) {&lt;br /&gt;
    var panel = card.querySelector(&#039;[data-profile-progress]&#039;);&lt;br /&gt;
    if (!panel || !summary) return;&lt;br /&gt;
&lt;br /&gt;
    var level = summary.level || 1;&lt;br /&gt;
    var totalXp = summary.totalXp || 0;&lt;br /&gt;
    var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
    var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
    var percent = Math.max(0, Math.min(100, summary.progressPercent || 0));&lt;br /&gt;
    var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
    var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
    var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
&lt;br /&gt;
    panel.classList.remove(&#039;is-syncing&#039;);&lt;br /&gt;
    panel.classList.toggle(&#039;is-max-level&#039;, isMaxLevel);&lt;br /&gt;
&lt;br /&gt;
    var levelEl = panel.querySelector(&#039;.profile-page-level&#039;);&lt;br /&gt;
    var totalEl = panel.querySelector(&#039;.profile-page-total-xp&#039;);&lt;br /&gt;
    var fillEl = panel.querySelector(&#039;.profile-page-xp-fill&#039;);&lt;br /&gt;
    var subEl = panel.querySelector(&#039;.profile-page-progress-sub&#039;);&lt;br /&gt;
    var metaEl = panel.querySelector(&#039;.profile-page-progress-meta&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (levelEl) levelEl.textContent = (isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level;&lt;br /&gt;
    if (totalEl) totalEl.textContent = totalXp + &#039; XP&#039;;&lt;br /&gt;
    if (fillEl) fillEl.style.width = percent + &#039;%&#039;;&lt;br /&gt;
    if (subEl) subEl.textContent = isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;);&lt;br /&gt;
    if (metaEl) metaEl.textContent = &#039;TODAY &#039; + dailyXp + &#039; XP · DISCOVERED &#039; + discoveries;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileFields(username, card) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;userprofile&#039;,&lt;br /&gt;
        user: username&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var profile = data.userprofile;&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: profile.name || &#039;&#039;,&lt;br /&gt;
            discord: profile.discord || &#039;&#039;,&lt;br /&gt;
            role: profile.role || &#039;&#039;,&lt;br /&gt;
            bio: profile.bio || &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    }).fail(function() {&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: &#039;&#039;,&lt;br /&gt;
            discord: &#039;&#039;,&lt;br /&gt;
            role: &#039;&#039;,&lt;br /&gt;
            bio: &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfileFields(card, data) {&lt;br /&gt;
    var nameEl = card.querySelector(&#039;[data-field=&amp;quot;name&amp;quot;]&#039;);&lt;br /&gt;
    var roleEl = card.querySelector(&#039;[data-field=&amp;quot;role&amp;quot;]&#039;);&lt;br /&gt;
    var discordEl = card.querySelector(&#039;[data-field=&amp;quot;discord&amp;quot;]&#039;);&lt;br /&gt;
    var bioEl = card.querySelector(&#039;[data-field=&amp;quot;bio&amp;quot;]&#039;);&lt;br /&gt;
    if (nameEl) nameEl.textContent = data.name || &#039;&#039;;&lt;br /&gt;
    if (roleEl) roleEl.textContent = data.role || &#039;&#039;;&lt;br /&gt;
    if (discordEl) discordEl.textContent = data.discord ? (&#039;디스코드: &#039; + data.discord) : &#039;&#039;;&lt;br /&gt;
    if (bioEl) bioEl.textContent = data.bio || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
// ========== 프로필 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
// ========== 알림 시스템 ==========&lt;br /&gt;
function ensureNotificationPopup() {&lt;br /&gt;
    if (document.getElementById(&#039;clbi-notification-popup&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    var popup = document.createElement(&#039;div&#039;);&lt;br /&gt;
    popup.id = &#039;clbi-notification-popup&#039;;&lt;br /&gt;
    popup.style.cssText =&lt;br /&gt;
        &#039;display:none;position:fixed;z-index:99999;width:320px;max-height:420px;&#039; +&lt;br /&gt;
        &#039;background:#0a0909;border:2px solid #854369;border-radius:5px;&#039; +&lt;br /&gt;
        &#039;box-shadow:0 0 0 1px #1a1a1a, 0 8px 24px rgba(0,0,0,0.55);overflow:hidden;&#039;;&lt;br /&gt;
&lt;br /&gt;
    popup.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:10px 12px;border-bottom:2px solid #854369;background:linear-gradient(to bottom, #171114 0%, #0a0909 100%);color:#E2E2E2;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:space-between;gap:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;알림&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-notification-readall&amp;quot; style=&amp;quot;background:#171717;border:1px solid #854369;border-radius:6px;color:#E2E2E2;font-size:11px;font-weight:700;padding:4px 8px;cursor:pointer;&amp;quot;&amp;gt;전체 읽음&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;clbi-notification-list&amp;quot; style=&amp;quot;max-height:320px;overflow-y:auto;padding:8px 0;color:#E2E2E2;font-size:12px;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:8px;border-top:1px solid #2a2a2a;background:#111;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;/index.php?title=Special:Notifications&amp;quot; id=&amp;quot;clbi-notification-more&amp;quot; style=&amp;quot;display:block;width:100%;text-align:center;padding:8px 10px;border-radius:6px;background:#171717;border:1px solid #854369;color:#E2E2E2 !important;text-decoration:none !important;font-size:12px;font-weight:700;&amp;quot;&amp;gt;더보기&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    document.body.appendChild(popup);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function positionNotificationPopup() {&lt;br /&gt;
    var btn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
    var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
    if (!btn || !popup) return;&lt;br /&gt;
&lt;br /&gt;
    var rect = btn.getBoundingClientRect();&lt;br /&gt;
    var top = rect.bottom + 6;&lt;br /&gt;
    var left = rect.left + (rect.width / 2) - (popup.offsetWidth / 2);&lt;br /&gt;
&lt;br /&gt;
    if (left &amp;lt; 8) left = 8;&lt;br /&gt;
    if (left + popup.offsetWidth &amp;gt; window.innerWidth - 8) {&lt;br /&gt;
        left = window.innerWidth - popup.offsetWidth - 8;&lt;br /&gt;
    }&lt;br /&gt;
    if (top + popup.offsetHeight &amp;gt; window.innerHeight - 8) {&lt;br /&gt;
        top = Math.max(8, rect.top - popup.offsetHeight - 6);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    popup.style.top = top + &#039;px&#039;;&lt;br /&gt;
    popup.style.left = left + &#039;px&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseNotificationItemsFromHtml(html) {&lt;br /&gt;
    var parser = new DOMParser();&lt;br /&gt;
    var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.mw-echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;.mw-echo-ui-notificationsInboxWidgetRow&#039;,&lt;br /&gt;
        &#039;.echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;li[data-notification-id]&#039;,&lt;br /&gt;
        &#039;.mw-echo-notifications-list li&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var items = [];&lt;br /&gt;
    for (var i = 0; i &amp;lt; selectors.length; i++) {&lt;br /&gt;
        items = Array.prototype.slice.call(doc.querySelectorAll(selectors[i]));&lt;br /&gt;
        if (items.length) break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return items.slice(0, 5).map(function(item) {&lt;br /&gt;
        var link = item.querySelector(&#039;a[href]&#039;);&lt;br /&gt;
        var href = link ? link.getAttribute(&#039;href&#039;) : &#039;/index.php?title=Special:Notifications&#039;;&lt;br /&gt;
        var text = (item.textContent || &#039;&#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        var notificationId =&lt;br /&gt;
            item.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
            &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!notificationId) {&lt;br /&gt;
            var anyWithId = item.querySelector(&#039;[data-notification-id], [data-id], [data-notification]&#039;);&lt;br /&gt;
            if (anyWithId) {&lt;br /&gt;
                notificationId =&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (href &amp;amp;&amp;amp; href.indexOf(&#039;http&#039;) !== 0) {&lt;br /&gt;
            href = href.charAt(0) === &#039;/&#039;&lt;br /&gt;
                ? href&lt;br /&gt;
                : &#039;/index.php&#039; + (href.charAt(0) === &#039;?&#039; ? href : &#039;/&#039; + href);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            id: notificationId,&lt;br /&gt;
            href: href,&lt;br /&gt;
            text: text || &#039;알림&#039;&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setNotificationIcon(hasItems) {&lt;br /&gt;
    var quickIcon = document.getElementById(&#039;profile-quick-notification-icon&#039;);&lt;br /&gt;
    var svg = hasItems ? CLBI_SVG_BELL_DOT : CLBI_SVG_BELL;&lt;br /&gt;
&lt;br /&gt;
    if (quickIcon) {&lt;br /&gt;
        quickIcon.innerHTML = svg;&lt;br /&gt;
        quickIcon.classList.toggle(&#039;has-notifications&#039;, !!hasItems);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderNotificationPopup(items) {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    var badge = document.getElementById(&#039;clbi-notification-badge&#039;);&lt;br /&gt;
    if (!list) return;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;표시할 알림이 없습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        if (badge) badge.style.display = &#039;none&#039;;&lt;br /&gt;
        setNotificationIcon(false);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
    for (var i = 0; i &amp;lt; items.length; i++) {&lt;br /&gt;
        html +=&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;&#039; + items[i].href + &#039;&amp;quot; class=&amp;quot;clbi-notification-item&amp;quot; data-notification-id=&amp;quot;&#039; + (items[i].id || &#039;&#039;) + &#039;&amp;quot; style=&amp;quot;display:block;padding:10px 12px;color:#E2E2E2 !important;text-decoration:none !important;border-bottom:1px solid #1f1f1f;line-height:1.5;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                items[i].text +&lt;br /&gt;
            &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    list.innerHTML = html;&lt;br /&gt;
&lt;br /&gt;
    if (badge) {&lt;br /&gt;
        badge.textContent = items.length;&lt;br /&gt;
        badge.style.display = &#039;block&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    setNotificationIcon(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadNotificationsIntoPopup() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetch(&#039;/index.php?title=Special:Notifications&#039;, { credentials: &#039;same-origin&#039; })&lt;br /&gt;
        .then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        })&lt;br /&gt;
        .then(function(html) {&lt;br /&gt;
            var items = parseNotificationItemsFromHtml(html);&lt;br /&gt;
            renderNotificationPopup(items);&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function(err) {&lt;br /&gt;
            console.error(err);&lt;br /&gt;
            if (list) {&lt;br /&gt;
                list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;알림을 불러오지 못했습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markAllNotificationsRead() {&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: &#039;all&#039;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markNotificationReadById(notificationId) {&lt;br /&gt;
    if (!notificationId) {&lt;br /&gt;
        return $.Deferred().resolve().promise();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: notificationId&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initNotifications() {&lt;br /&gt;
    var quickBtn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!quickBtn) return;&lt;br /&gt;
&lt;br /&gt;
    ensureNotificationPopup();&lt;br /&gt;
    loadNotificationsIntoPopup();&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationToggle&#039;, &#039;#profile-quick-notifications&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (popup.style.display === &#039;none&#039; || popup.style.display === &#039;&#039;) {&lt;br /&gt;
                popup.style.display = &#039;block&#039;;&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
            } else {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationOutside&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationOutside&#039;, function(e) {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            var quickToggle = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (!popup.contains(e.target) &amp;amp;&amp;amp; (!quickToggle || !quickToggle.contains(e.target))) {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationReadAll&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationReadAll&#039;, &#039;#clbi-notification-readall&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var button = this;&lt;br /&gt;
            button.disabled = true;&lt;br /&gt;
            button.textContent = &#039;처리 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            markAllNotificationsRead()&lt;br /&gt;
                .then(function() {&lt;br /&gt;
                    loadNotificationsIntoPopup();&lt;br /&gt;
                })&lt;br /&gt;
                .always(function() {&lt;br /&gt;
                    button.disabled = false;&lt;br /&gt;
                    button.textContent = &#039;전체 읽음&#039;;&lt;br /&gt;
                });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationItem&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationItem&#039;, &#039;.clbi-notification-item&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var href = this.getAttribute(&#039;href&#039;);&lt;br /&gt;
            var notificationId = this.getAttribute(&#039;data-notification-id&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            markNotificationReadById(notificationId).always(function() {&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
                if (href) {&lt;br /&gt;
                    window.location.href = href;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(window)&lt;br /&gt;
        .off(&#039;resize.clbiNotification&#039;)&lt;br /&gt;
        .on(&#039;resize.clbiNotification&#039;, function() {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (popup &amp;amp;&amp;amp; popup.style.display === &#039;block&#039;) {&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
// ========== 알림 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
function initUserProfilePage() {&lt;br /&gt;
    $(&#039;body&#039;).addClass(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
&lt;br /&gt;
    var saveBtn = document.getElementById(&#039;pref-save&#039;);&lt;br /&gt;
    if (!saveBtn) return;&lt;br /&gt;
&lt;br /&gt;
    function getPrefRow(id) {&lt;br /&gt;
        var el = document.getElementById(id);&lt;br /&gt;
        if (!el) return null;&lt;br /&gt;
        return el.closest(&#039;.clbi-pref-row&#039;) || el.parentNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removePrefRow(id) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (row &amp;amp;&amp;amp; row.parentNode) {&lt;br /&gt;
            row.parentNode.removeChild(row);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPrefSection(className, titleText) {&lt;br /&gt;
        var section = document.createElement(&#039;div&#039;);&lt;br /&gt;
        section.className = &#039;clbi-pref-section &#039; + className;&lt;br /&gt;
&lt;br /&gt;
        var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;clbi-pref-section-title&#039;;&lt;br /&gt;
        title.textContent = titleText;&lt;br /&gt;
&lt;br /&gt;
        var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
        body.className = &#039;clbi-pref-section-body&#039;;&lt;br /&gt;
&lt;br /&gt;
        section.appendChild(title);&lt;br /&gt;
        section.appendChild(body);&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            section: section,&lt;br /&gt;
            body: body&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveRowToSection(id, targetBody, className) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (!row || !targetBody) return false;&lt;br /&gt;
&lt;br /&gt;
        row.classList.add(&#039;clbi-pref-row-key-&#039; + className);&lt;br /&gt;
        targetBody.appendChild(row);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebuildProfileSettingsLayout() {&lt;br /&gt;
        var root = document.querySelector(&#039;.clbi-prefs-profile&#039;);&lt;br /&gt;
        if (!root || root.dataset.profileSettingsReworked === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        root.dataset.profileSettingsReworked = &#039;1&#039;;&lt;br /&gt;
        root.classList.add(&#039;profile-settings-console&#039;);&lt;br /&gt;
&lt;br /&gt;
        removePrefRow(&#039;pref-badges&#039;);&lt;br /&gt;
&lt;br /&gt;
        var originalRows = Array.prototype.slice.call(root.querySelectorAll(&#039;.clbi-pref-row&#039;));&lt;br /&gt;
        var actionNodes = [];&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn.parentNode === root || saveBtn.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(saveBtn);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var statusNode = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        if (statusNode &amp;amp;&amp;amp; statusNode.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(statusNode);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var main = document.createElement(&#039;div&#039;);&lt;br /&gt;
        main.className = &#039;clbi-pref-main-grid&#039;;&lt;br /&gt;
&lt;br /&gt;
        var media = createPrefSection(&#039;clbi-pref-section-media&#039;, &#039;PROFILE IMAGE&#039;);&lt;br /&gt;
        var identity = createPrefSection(&#039;clbi-pref-section-identity&#039;, &#039;IDENTITY RECORD&#039;);&lt;br /&gt;
        var bio = createPrefSection(&#039;clbi-pref-section-bio&#039;, &#039;BIOGRAPHY&#039;);&lt;br /&gt;
        var account = createPrefSection(&#039;clbi-pref-section-account&#039;, &#039;ACCOUNT CONTACT&#039;);&lt;br /&gt;
        var misc = createPrefSection(&#039;clbi-pref-section-misc&#039;, &#039;OTHER OPTIONS&#039;);&lt;br /&gt;
&lt;br /&gt;
        main.appendChild(media.section);&lt;br /&gt;
        main.appendChild(identity.section);&lt;br /&gt;
        main.appendChild(bio.section);&lt;br /&gt;
        main.appendChild(account.section);&lt;br /&gt;
        main.appendChild(misc.section);&lt;br /&gt;
&lt;br /&gt;
        root.innerHTML = &#039;&#039;;&lt;br /&gt;
        root.appendChild(main);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-preview&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-btn&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-input&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-name&#039;, identity.body, &#039;name&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-role&#039;, identity.body, &#039;role&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-discord&#039;, identity.body, &#039;discord&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-bio&#039;, bio.body, &#039;bio&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-new-email&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-password&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-save&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
&lt;br /&gt;
        originalRows.forEach(function (row) {&lt;br /&gt;
            if (!row.parentNode &amp;amp;&amp;amp; !row.className.match(/clbi-pref-row-key-/)) {&lt;br /&gt;
                misc.body.appendChild(row);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!misc.body.children.length) {&lt;br /&gt;
            misc.section.parentNode.removeChild(misc.section);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var actions = document.createElement(&#039;div&#039;);&lt;br /&gt;
        actions.className = &#039;clbi-pref-actions&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn) actions.appendChild(saveBtn);&lt;br /&gt;
        if (statusNode) actions.appendChild(statusNode);&lt;br /&gt;
&lt;br /&gt;
        root.appendChild(actions);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    rebuildProfileSettingsLayout();&lt;br /&gt;
&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    var selectedFile = null;&lt;br /&gt;
    var cropper = null;&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-gallery-modal&#039;)) {&lt;br /&gt;
        var gModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        gModal.id = &#039;clbi-gallery-modal&#039;;&lt;br /&gt;
        gModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        gModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:480px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;justify-content:space-between;align-items:center;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;프로필 사진 선택&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-close&amp;quot; style=&amp;quot;background:none;border:none;color:#aaa;font-size:18px;cursor:pointer;&amp;quot;&amp;gt;✕&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-upload-btn&amp;quot; style=&amp;quot;background:#2a2a2a;border:2px dashed #854369;border-radius:8px;padding:32px;color:#e2e2e2;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:8px;font-size:13px;width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:32px;&amp;quot;&amp;gt;🖼️&amp;lt;/span&amp;gt;새 사진 업로드&#039; +&lt;br /&gt;
                &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history-section&amp;quot; style=&amp;quot;display:none;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div style=&amp;quot;font-size:11px;color:#888;margin-bottom:8px;&amp;quot;&amp;gt;이전 사진 — 클릭하면 바로 적용&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history&amp;quot; style=&amp;quot;display:flex;gap:8px;flex-wrap:wrap;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(gModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-crop-modal&#039;)) {&lt;br /&gt;
        var cModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        cModal.id = &#039;clbi-crop-modal&#039;;&lt;br /&gt;
        cModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        cModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:500px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;사진 조정&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;width:100%;max-height:380px;overflow:hidden;border-radius:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img id=&amp;quot;clbi-crop-image&amp;quot; style=&amp;quot;max-width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;gap:8px;justify-content:flex-end;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-cancel&amp;quot; style=&amp;quot;background:#2a2a2a;color:#e2e2e2;border:1px solid #444;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;취소&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-confirm&amp;quot; style=&amp;quot;background:#854369;color:#fff;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;확정&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(cModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gModal = document.getElementById(&#039;clbi-gallery-modal&#039;);&lt;br /&gt;
    var cModal = document.getElementById(&#039;clbi-crop-modal&#039;);&lt;br /&gt;
    var cropImage = document.getElementById(&#039;clbi-crop-image&#039;);&lt;br /&gt;
    var pfpInput = document.getElementById(&#039;pref-pfp-input&#039;);&lt;br /&gt;
&lt;br /&gt;
    function openGallery() {&lt;br /&gt;
        gModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;query&#039;,&lt;br /&gt;
            titles: &#039;파일:Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
            prop: &#039;imageinfo&#039;,&lt;br /&gt;
            iiprop: &#039;url|timestamp&#039;,&lt;br /&gt;
            iilimit: 6&lt;br /&gt;
        }).then(function(data) {&lt;br /&gt;
            var pages = data.query.pages;&lt;br /&gt;
            var page = pages[Object.keys(pages)[0]];&lt;br /&gt;
            if (!page.imageinfo || page.imageinfo.length === 0) return;&lt;br /&gt;
&lt;br /&gt;
            var historyEl = document.getElementById(&#039;clbi-gallery-history&#039;);&lt;br /&gt;
            var sectionEl = document.getElementById(&#039;clbi-gallery-history-section&#039;);&lt;br /&gt;
            historyEl.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            page.imageinfo.forEach(function(info, idx) {&lt;br /&gt;
                var wrap = document.createElement(&#039;div&#039;);&lt;br /&gt;
                wrap.style.cssText = &#039;position:relative;cursor:pointer;&#039;;&lt;br /&gt;
&lt;br /&gt;
                var img = document.createElement(&#039;img&#039;);&lt;br /&gt;
                img.src = info.url;&lt;br /&gt;
                img.style.cssText =&lt;br /&gt;
                    &#039;width:72px;height:72px;object-fit:cover;border-radius:8px;border:2px solid #444;flex-shrink:0;&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (idx === 0) {&lt;br /&gt;
                    img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                    var badge = document.createElement(&#039;div&#039;);&lt;br /&gt;
                    badge.textContent = &#039;현재&#039;;&lt;br /&gt;
                    badge.style.cssText =&lt;br /&gt;
                        &#039;position:absolute;bottom:4px;left:50%;transform:translateX(-50%);background:#854369;color:#fff;font-size:9px;padding:1px 6px;border-radius:10px;&#039;;&lt;br /&gt;
                    wrap.appendChild(badge);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseenter&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseleave&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#444&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
                    fetch(info.url)&lt;br /&gt;
                        .then(function(r) {&lt;br /&gt;
                            return r.blob();&lt;br /&gt;
                        })&lt;br /&gt;
                        .then(function(blob) {&lt;br /&gt;
                            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
                            gModal.style.display = &#039;none&#039;;&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                wrap.appendChild(img);&lt;br /&gt;
                historyEl.appendChild(wrap);&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            sectionEl.style.display = &#039;block&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function openCrop(src) {&lt;br /&gt;
        cropImage.src = src;&lt;br /&gt;
        cModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            cropper = new Cropper(cropImage, {&lt;br /&gt;
                aspectRatio: 1,&lt;br /&gt;
                viewMode: 1,&lt;br /&gt;
                dragMode: &#039;move&#039;,&lt;br /&gt;
                autoCropArea: 0.8,&lt;br /&gt;
                cropBoxResizable: true,&lt;br /&gt;
                cropBoxMovable: true&lt;br /&gt;
            });&lt;br /&gt;
        }, 150);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;pref-pfp-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        openGallery();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-upload-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        pfpInput.click();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-close&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    pfpInput.addEventListener(&#039;change&#039;, function() {&lt;br /&gt;
        var file = this.files[0];&lt;br /&gt;
        if (!file) return;&lt;br /&gt;
&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
&lt;br /&gt;
        var reader = new FileReader();&lt;br /&gt;
        reader.onload = function(e) {&lt;br /&gt;
            openCrop(e.target.result);&lt;br /&gt;
        };&lt;br /&gt;
        reader.readAsDataURL(file);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-cancel&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        cModal.style.display = &#039;none&#039;;&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
        pfpInput.value = &#039;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-confirm&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        if (!cropper) return;&lt;br /&gt;
&lt;br /&gt;
        var canvas = cropper.getCroppedCanvas({ width: 256, height: 256 });&lt;br /&gt;
        if (!canvas) return;&lt;br /&gt;
&lt;br /&gt;
        canvas.toBlob(function(blob) {&lt;br /&gt;
            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
            cModal.style.display = &#039;none&#039;;&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
        }, &#039;image/png&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var emailSaveBtn = document.getElementById(&#039;pref-email-save&#039;);&lt;br /&gt;
    if (emailSaveBtn) {&lt;br /&gt;
        emailSaveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
            var statusEl = document.getElementById(&#039;pref-email-status&#039;);&lt;br /&gt;
            var newEmail = document.getElementById(&#039;pref-new-email&#039;).value;&lt;br /&gt;
            var password = document.getElementById(&#039;pref-email-password&#039;).value;&lt;br /&gt;
&lt;br /&gt;
            if (!newEmail || !password) {&lt;br /&gt;
                statusEl.textContent = &#039;이메일과 비밀번호를 입력해주세요.&#039;;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            statusEl.textContent = &#039;변경 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;changeemail&#039;,&lt;br /&gt;
                email: newEmail,&lt;br /&gt;
                password: password&lt;br /&gt;
            }).then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 이메일 변경됨&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-new-email&#039;).value = &#039;&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-email-password&#039;).value = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 3000);&lt;br /&gt;
            }).fail(function(code, data) {&lt;br /&gt;
                var msg = data &amp;amp;&amp;amp; data.error &amp;amp;&amp;amp; data.error.info ? data.error.info : &#039;변경 실패&#039;;&lt;br /&gt;
                statusEl.textContent = msg;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    saveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        var statusEl = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        statusEl.textContent = &#039;저장 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
        var promises = [];&lt;br /&gt;
&lt;br /&gt;
        if (selectedFile) {&lt;br /&gt;
            var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;upload&#039;,&lt;br /&gt;
                    filename: &#039;Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
                    ignorewarnings: true,&lt;br /&gt;
                    file: selectedFile,&lt;br /&gt;
                    format: &#039;json&#039;&lt;br /&gt;
                }, {&lt;br /&gt;
                    contentType: &#039;multipart/form-data&#039;&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var fields = [&#039;name&#039;, &#039;discord&#039;, &#039;role&#039;, &#039;bio&#039;];&lt;br /&gt;
&lt;br /&gt;
        for (var i = 0; i &amp;lt; fields.length; i++) {&lt;br /&gt;
            var el = document.getElementById(&#039;pref-&#039; + fields[i]);&lt;br /&gt;
            if (!el) continue;&lt;br /&gt;
&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;options&#039;,&lt;br /&gt;
                    optionname: &#039;profile-&#039; + fields[i],&lt;br /&gt;
                    optionvalue: el.value&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $.when.apply($, promises)&lt;br /&gt;
            .then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 저장됨&#039;;&lt;br /&gt;
                selectedFile = null;&lt;br /&gt;
                document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;사진 선택&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 2000);&lt;br /&gt;
            })&lt;br /&gt;
            .fail(function() {&lt;br /&gt;
                statusEl.textContent = &#039;저장 실패&#039;;&lt;br /&gt;
            });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Banner / CRT Page Monitor thumbnail slices&lt;br /&gt;
   - base 이미지는 틀에 들어간 파일 문법 그대로 사용&lt;br /&gt;
   - slice 레이어에는 300px MediaWiki 썸네일만 삽입&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function ($, mw) {&lt;br /&gt;
    var thumbCache = {};&lt;br /&gt;
&lt;br /&gt;
    function parseSliceWidth(value) {&lt;br /&gt;
        var parsed = parseInt(value, 10);&lt;br /&gt;
&lt;br /&gt;
        if (!isFinite(parsed) || parsed &amp;lt; 120) {&lt;br /&gt;
            return 300;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parsed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getImageSrc(img) {&lt;br /&gt;
        return img ? (img.currentSrc || img.getAttribute(&#039;src&#039;) || img.src || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFileNameFromSrc(src) {&lt;br /&gt;
        var a;&lt;br /&gt;
        var parts;&lt;br /&gt;
        var fileName;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = src;&lt;br /&gt;
&lt;br /&gt;
        parts = (a.pathname || &#039;&#039;).split(&#039;/&#039;).filter(function (part) {&lt;br /&gt;
            return !!part;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!parts.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        fileName = parts.pop();&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * MediaWiki thumb URL 예시:&lt;br /&gt;
         * /images/thumb/a/ab/File.png/1000px-File.png&lt;br /&gt;
         * /images/thumb/a/ab/File.svg/1000px-File.svg.png&lt;br /&gt;
         *&lt;br /&gt;
         * 이 경우 실제 파일명은 마지막 조각이 아니라 그 앞 조각이다.&lt;br /&gt;
         */&lt;br /&gt;
        if (/^\d+px-/.test(fileName) &amp;amp;&amp;amp; parts.length) {&lt;br /&gt;
            fileName = parts.pop();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fileName = fileName.replace(/^\d+px-/, &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            fileName = decodeURIComponent(fileName);&lt;br /&gt;
        } catch (e) {}&lt;br /&gt;
&lt;br /&gt;
        return fileName;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveThumbUrl(img, width, callback) {&lt;br /&gt;
        var src = getImageSrc(img);&lt;br /&gt;
        var fileName = getFileNameFromSrc(src);&lt;br /&gt;
        var cacheKey;&lt;br /&gt;
        var entry;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return;&lt;br /&gt;
&lt;br /&gt;
        if (!fileName || !mw || !mw.loader) {&lt;br /&gt;
            callback(src);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        cacheKey = fileName + &#039;|&#039; + width;&lt;br /&gt;
        entry = thumbCache[cacheKey];&lt;br /&gt;
&lt;br /&gt;
        if (entry) {&lt;br /&gt;
            if (entry.resolved) {&lt;br /&gt;
                callback(entry.url || src);&lt;br /&gt;
            } else {&lt;br /&gt;
                entry.callbacks.push(callback);&lt;br /&gt;
            }&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        entry = {&lt;br /&gt;
            resolved: false,&lt;br /&gt;
            url: &#039;&#039;,&lt;br /&gt;
            callbacks: [callback]&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        thumbCache[cacheKey] = entry;&lt;br /&gt;
&lt;br /&gt;
        function finish(url) {&lt;br /&gt;
            var callbacks = entry.callbacks.slice();&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            entry.resolved = true;&lt;br /&gt;
            entry.url = url || src;&lt;br /&gt;
            entry.callbacks = [];&lt;br /&gt;
&lt;br /&gt;
            for (i = 0; i &amp;lt; callbacks.length; i++) {&lt;br /&gt;
                callbacks[i](entry.url);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using(&#039;mediawiki.api&#039;).done(function () {&lt;br /&gt;
            var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + fileName,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url&#039;,&lt;br /&gt;
                iiurlwidth: width,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).done(function (data) {&lt;br /&gt;
                var page;&lt;br /&gt;
                var info;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    data &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages.length&lt;br /&gt;
                ) {&lt;br /&gt;
                    page = data.query.pages[0];&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        page &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo.length&lt;br /&gt;
                    ) {&lt;br /&gt;
                        info = page.imageinfo[0];&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                finish((info &amp;amp;&amp;amp; (info.thumburl || info.url)) || src);&lt;br /&gt;
            }).fail(function () {&lt;br /&gt;
                finish(src);&lt;br /&gt;
            });&lt;br /&gt;
        }).fail(function () {&lt;br /&gt;
            finish(src);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySliceImages(frame, thumbUrl) {&lt;br /&gt;
        var slices;&lt;br /&gt;
        var i;&lt;br /&gt;
        var img;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !thumbUrl) return;&lt;br /&gt;
&lt;br /&gt;
        slices = frame.querySelectorAll(&#039;.crt-page-monitor-slice&#039;);&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; slices.length; i++) {&lt;br /&gt;
            slices[i].innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            img = document.createElement(&#039;img&#039;);&lt;br /&gt;
            img.className = &#039;crt-page-monitor-slice-img&#039;;&lt;br /&gt;
            img.src = thumbUrl;&lt;br /&gt;
            img.alt = &#039;&#039;;&lt;br /&gt;
            img.decoding = &#039;async&#039;;&lt;br /&gt;
            img.loading = &#039;eager&#039;;&lt;br /&gt;
            img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
            slices[i].appendChild(img);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        frame.setAttribute(&#039;data-crt-slices-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrame(frame) {&lt;br /&gt;
        var baseImg;&lt;br /&gt;
        var width;&lt;br /&gt;
&lt;br /&gt;
        if (!frame) return;&lt;br /&gt;
        if (frame.getAttribute(&#039;data-crt-slices-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        baseImg = frame.querySelector(&#039;.crt-page-monitor-image-base img&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!baseImg) return;&lt;br /&gt;
&lt;br /&gt;
        width = parseSliceWidth(frame.getAttribute(&#039;data-crt-slice-width&#039;));&lt;br /&gt;
&lt;br /&gt;
        resolveThumbUrl(baseImg, width, function (thumbUrl) {&lt;br /&gt;
            if (!frame || !frame.parentNode) return;&lt;br /&gt;
            applySliceImages(frame, thumbUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrames(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var frames = scope.querySelectorAll(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; frames.length; i++) {&lt;br /&gt;
            initBannerFrame(frames[i]);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initBannerFrames(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initBannerFrames($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(jQuery, window.mw);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Tab System — tab switching UI&lt;br /&gt;
   글리치 플리커 + RGB split + 방향 슬라이드&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function initDocTabs() {&lt;br /&gt;
        var tabBars = document.querySelectorAll(&#039;.doc-tab-bar&#039;);&lt;br /&gt;
        if (!tabBars.length) return;&lt;br /&gt;
&lt;br /&gt;
        tabBars.forEach(function (bar) {&lt;br /&gt;
            if (bar.getAttribute(&#039;data-tabs-init&#039;)) return;&lt;br /&gt;
            bar.setAttribute(&#039;data-tabs-init&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            var tabs = Array.from(bar.querySelectorAll(&#039;.doc-tab&#039;));&lt;br /&gt;
            if (!tabs.length) return;&lt;br /&gt;
&lt;br /&gt;
            var panel = bar.closest(&#039;.doc-panel&#039;);&lt;br /&gt;
            var display = panel ? panel.querySelector(&#039;.doc-display&#039;) : null;&lt;br /&gt;
            if (!display) display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
            if (!display) return;&lt;br /&gt;
&lt;br /&gt;
            tabs.forEach(function (tab, i) {&lt;br /&gt;
                tab.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
                    var currentIdx = tabs.findIndex(function (t) {&lt;br /&gt;
                        return t.classList.contains(&#039;active&#039;);&lt;br /&gt;
                    });&lt;br /&gt;
                    if (currentIdx === i) return;&lt;br /&gt;
                    switchTab(tabs, display, i, i &amp;gt; currentIdx ? 1 : -1);&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            var initIdx = tabs.findIndex(function (t) { return t.classList.contains(&#039;active&#039;); });&lt;br /&gt;
            if (initIdx !== -1) {&lt;br /&gt;
                var initRef = tabs[initIdx].dataset.ref;&lt;br /&gt;
                var initEl = initRef ? document.getElementById(initRef) : null;&lt;br /&gt;
                display.innerHTML = initEl ? initEl.innerHTML : (tabs[initIdx].dataset.content || &#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var isAnimating = false;&lt;br /&gt;
&lt;br /&gt;
    function switchTab(tabs, display, nextIdx, dir) {&lt;br /&gt;
        if (isAnimating) return;&lt;br /&gt;
        isAnimating = true;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (t) { t.classList.remove(&#039;active&#039;); });&lt;br /&gt;
        tabs[nextIdx].classList.add(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
        var ref = tabs[nextIdx].dataset.ref;&lt;br /&gt;
        var nextContent;&lt;br /&gt;
        if (ref) {&lt;br /&gt;
            var refEl = document.getElementById(ref);&lt;br /&gt;
            nextContent = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            nextContent = tabs[nextIdx].dataset.content || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        glitchOut(display, dir, function () {&lt;br /&gt;
            display.innerHTML = nextContent;&lt;br /&gt;
            glitchIn(display, dir, function () {&lt;br /&gt;
                isAnimating = false;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchOut(el, dir, cb) {&lt;br /&gt;
        var duration = 160;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var slideX = dir * 16;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = p * p;&lt;br /&gt;
&lt;br /&gt;
            var tx = slideX * ease;&lt;br /&gt;
            var skew = dir * ease * 1.0;&lt;br /&gt;
            var opacity = 1 - ease;&lt;br /&gt;
            var rgb = ease * 5;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.75)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.65)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + (1 + ease * 0.25) + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.opacity = &#039;0&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchIn(el, dir, cb) {&lt;br /&gt;
        var duration = 200;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var startX = -dir * 16;&lt;br /&gt;
&lt;br /&gt;
        el.style.transform = &#039;translateX(&#039; + startX + &#039;px) skewX(&#039; + (-dir * 1.0) + &#039;deg)&#039;;&lt;br /&gt;
        el.style.opacity = &#039;0&#039;;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = 1 - Math.pow(1 - p, 3);&lt;br /&gt;
&lt;br /&gt;
            var tx = startX * (1 - ease);&lt;br /&gt;
            var skew = -dir * 1.0 * (1 - ease);&lt;br /&gt;
            var opacity = ease;&lt;br /&gt;
            var rgb = (1 - ease) * 3;&lt;br /&gt;
            var brightness = 1 + (1 - ease) * 0.35;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.65)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.55)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + brightness + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.transform = &#039;&#039;;&lt;br /&gt;
                el.style.opacity = &#039;&#039;;&lt;br /&gt;
                el.style.filter = &#039;&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initDocTabs);&lt;br /&gt;
} else {&lt;br /&gt;
    initDocTabs();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        initDocTabs();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Section Switch — 좌측 섹션 전환&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;.doc-nav-item[data-section]&#039;, function () {&lt;br /&gt;
    var name = $(this).attr(&#039;data-section&#039;);&lt;br /&gt;
    var display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
    var titleEl = document.getElementById(&#039;doc-center-title&#039;);&lt;br /&gt;
    var tabBar = document.getElementById(&#039;doc-tab-bar-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!display) return;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section]&#039;).removeClass(&#039;active&#039;);&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section=&amp;quot;&#039; + name + &#039;&amp;quot;]&#039;).addClass(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (name === &#039;text&#039;) {&lt;br /&gt;
        if (titleEl) titleEl.textContent = &#039;개요&#039;;&lt;br /&gt;
        if (tabBar) $(tabBar).show();&lt;br /&gt;
        var activeTab = tabBar ? tabBar.querySelector(&#039;.doc-tab.active&#039;) : null;&lt;br /&gt;
        if (!activeTab &amp;amp;&amp;amp; tabBar) activeTab = tabBar.querySelector(&#039;.doc-tab&#039;);&lt;br /&gt;
        if (activeTab) {&lt;br /&gt;
            var ref = activeTab.dataset.ref;&lt;br /&gt;
            var refEl = ref ? document.getElementById(ref) : null;&lt;br /&gt;
            display.innerHTML = refEl ? refEl.innerHTML : (activeTab.dataset.content || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        if (titleEl) titleEl.textContent = name === &#039;factions&#039; ? &#039;세력&#039; : name === &#039;people&#039; ? &#039;인물&#039; : name;&lt;br /&gt;
        if (tabBar) $(tabBar).hide();&lt;br /&gt;
        var refEl = document.getElementById(&#039;doc-content-&#039; + name);&lt;br /&gt;
        display.innerHTML = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   CRT WebGL Renderer — cool-retro-term IBM DOS style&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function createNoiseTexture(gl) {&lt;br /&gt;
        var size = 512;&lt;br /&gt;
        var data = new Uint8Array(size * size * 4);&lt;br /&gt;
        var s = 12345;&lt;br /&gt;
        function rand() {&lt;br /&gt;
            s = (s * 1664525 + 1013904223) &amp;amp; 0xffffffff;&lt;br /&gt;
            return (s &amp;gt;&amp;gt;&amp;gt; 0) / 0xffffffff;&lt;br /&gt;
        }&lt;br /&gt;
        for (var i = 0; i &amp;lt; data.length; i++) {&lt;br /&gt;
            data[i] = (rand() * 255) | 0;&lt;br /&gt;
        }&lt;br /&gt;
        var tex = gl.createTexture();&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, tex);&lt;br /&gt;
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
        return tex;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var VERT = [&lt;br /&gt;
        &#039;attribute vec2 a_pos;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5);&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_pos, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var FRAG = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_tex;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_noise;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_res;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_imgSize;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_noiseScale;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float sum2(vec2 v) { return v.x + v.y; }&#039;,&lt;br /&gt;
        &#039;float min2(vec2 v) { return min(v.x, v.y); }&#039;,&lt;br /&gt;
        &#039;float rgb2grey(vec3 v) { return dot(v, vec3(0.21, 0.72, 0.04)); }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 coverUV(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  float imgAR = u_imgSize.x / u_imgSize.y;&#039;,&lt;br /&gt;
        &#039;  float scrAR = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  float scale = imgAR / scrAR;&#039;,&lt;br /&gt;
        &#039;  float offsetY = (1.0 - scale) * 0.5;&#039;,&lt;br /&gt;
        &#039;  return vec2(uv.x, uv.y * scale + offsetY);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 barrel(vec2 v, vec2 cc, float k) {&#039;,&lt;br /&gt;
        &#039;  float ar = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  vec2 c2 = cc;&#039;,&lt;br /&gt;
        &#039;  if (ar &amp;gt; 1.0) c2.x /= ar; else c2.y *= ar;&#039;,&lt;br /&gt;
        &#039;  float dist = dot(c2, c2) * k;&#039;,&lt;br /&gt;
        &#039;  return v - cc * (1.0 + dist) * dist;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleInitialNoise(float t) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, vec2(fract(t/2048.0), fract(t/1048576.0)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleScreenNoise(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, u_noiseScale * uv);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyRgbShift(vec2 texUV, float shift) {&#039;,&lt;br /&gt;
        &#039;  vec2 d = vec2(shift, 0.0);&#039;,&lt;br /&gt;
        &#039;  vec3 r = texture2D(u_tex, clamp(texUV + d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 c = texture2D(u_tex, texUV).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 l = texture2D(u_tex, clamp(texUV - d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  return vec3(&#039;,&lt;br /&gt;
        &#039;    l.r*0.10 + r.r*0.30 + c.r*0.60,&#039;,&lt;br /&gt;
        &#039;    l.g*0.20 + r.g*0.20 + c.g*0.60,&#039;,&lt;br /&gt;
        &#039;    l.b*0.30 + r.b*0.10 + c.b*0.60&#039;,&lt;br /&gt;
        &#039;  );&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyBloom(vec2 texUV, float strength) {&#039;,&lt;br /&gt;
        &#039;  vec2 px = 2.0 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 acc = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0,  px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0, -px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  return acc / 6.0 * strength;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyScanlines(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
        &#039;  float line = mod(uv.y * u_res.y, 2.0);&#039;,&lt;br /&gt;
        &#039;  vec3 hi = ((1.0 + 0.30) - 0.2 * col) * col;&#039;,&lt;br /&gt;
        &#039;  vec3 lo = ((1.0 - 0.30) + 0.1 * col) * col;&#039;,&lt;br /&gt;
        &#039;  return line &amp;lt; 1.0 ? lo : hi;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
&#039;vec3 applyRasterization(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
&#039;  float t = u_time;&#039;,&lt;br /&gt;
&#039;  vec2 noiseUV = uv + vec2(fract(t * 0.030), fract(t * 0.060));&#039;,&lt;br /&gt;
&#039;  float wobbleX = (texture2D(u_noise, noiseUV * 0.8).r - 0.5) * 0.0018;&#039;,&lt;br /&gt;
&#039;  float wobbleY = (texture2D(u_noise, noiseUV * 0.8 + 0.5).r - 0.5) * 0.0008;&#039;,&lt;br /&gt;
&#039;  vec2 wobbledUV = clamp(uv + vec2(wobbleX, wobbleY), 0.0, 1.0);&#039;,&lt;br /&gt;
&#039;  vec3 wobbled = texture2D(u_tex, wobbledUV).rgb;&#039;,&lt;br /&gt;
&#039;  return mix(col, wobbled, 0.35);&#039;,&lt;br /&gt;
&#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float glowingLine(vec2 uv, float t) {&#039;,&lt;br /&gt;
&#039;  float pos = fract(t * 0.2);&#039;,&lt;br /&gt;
&#039;  float lineY = pos * (u_res.y + 330.0) - 120.0;&#039;,&lt;br /&gt;
        &#039;  float y = uv.y * u_res.y;&#039;,&lt;br /&gt;
        &#039;  return fract(smoothstep(-300.0, 0.0, y - lineY));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 applyHSync(vec2 uv, vec4 noise, float strength) {&#039;,&lt;br /&gt;
        &#039;  float randval = strength - noise.r;&#039;,&lt;br /&gt;
        &#039;  float scale = step(0.0, randval) * randval * strength;&#039;,&lt;br /&gt;
        &#039;  float freq = mix(4.0, 40.0, noise.g);&#039;,&lt;br /&gt;
        &#039;  uv.x += sin((uv.y + u_time * 0.001) * freq) * scale;&#039;,&lt;br /&gt;
        &#039;  return uv;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 cc = vec2(0.5) - v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float curvature = 0.18;&#039;,&lt;br /&gt;
        &#039;  vec2 uv = barrel(v_uv, cc, curvature);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float inScreen = min2(step(vec2(0.0), uv) - step(vec2(1.0), uv));&#039;,&lt;br /&gt;
        &#039;  if (inScreen &amp;lt; 0.5) { gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); return; }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 texUV = clamp(coverUV(uv), 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec4 initNoise = sampleInitialNoise(u_time);&#039;,&lt;br /&gt;
        &#039;  vec4 screenNoise = sampleScreenNoise(uv);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV = applyHSync(texUV, initNoise, 0.006);&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV += (vec2(screenNoise.b, screenNoise.a) - 0.5) * 0.0006;&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec3 col = applyRgbShift(texUV, 0.003);&#039;,&lt;br /&gt;
        &#039;  col += applyBloom(texUV, 0.22);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 bpx = 1.5 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 blurCol = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,   -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,    bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  col = mix(col, blurCol / 8.0, 0.40);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = applyScanlines(uv, col);&#039;,&lt;br /&gt;
        &#039;  col = applyRasterization(texUV, col);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float glow = glowingLine(uv, u_time);&#039;,&lt;br /&gt;
&#039;  col += glow * 0.08 * vec3(0.85, 0.95, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float dist = length(cc);&#039;,&lt;br /&gt;
        &#039;  col += screenNoise.a * 0.07 * (1.0 - dist * 1.3);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float grey = rgb2grey(col);&#039;,&lt;br /&gt;
        &#039;  vec3 phosphor = vec3(0.75, 0.88, 1.0);&#039;,&lt;br /&gt;
        &#039;  col = mix(col, grey * phosphor, 0.35);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 vig = v_uv * (1.0 - v_uv);&#039;,&lt;br /&gt;
        &#039;  col *= pow(vig.x * vig.y * 15.0, 0.25);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col *= 1.0 + (initNoise.g - 0.5) * 0.06;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col += vec3(0.012) * (1.0 - dist) * (1.0 - dist);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = pow(clamp(col, 0.0, 1.0), vec3(0.90));&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  gl_FragColor = vec4(col, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function initCRTCanvas(screen, imgEl) {&lt;br /&gt;
        var existing = screen.querySelector(&#039;.crt-webgl-canvas&#039;);&lt;br /&gt;
        if (existing) existing.remove();&lt;br /&gt;
&lt;br /&gt;
        var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.className = &#039;crt-webgl-canvas&#039;;&lt;br /&gt;
        canvas.style.cssText = &#039;position:absolute;inset:0;width:100%;height:100%;z-index:19;pointer-events:none;display:block;&#039;;&lt;br /&gt;
        screen.appendChild(canvas);&lt;br /&gt;
&lt;br /&gt;
        var gl = canvas.getContext(&#039;webgl&#039;) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
        if (!gl) return;&lt;br /&gt;
&lt;br /&gt;
        function compile(type, src) {&lt;br /&gt;
            var s = gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(s, src);&lt;br /&gt;
            gl.compileShader(s);&lt;br /&gt;
            if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {&lt;br /&gt;
                console.error(&#039;[CRT shader]&#039;, gl.getShaderInfoLog(s));&lt;br /&gt;
            }&lt;br /&gt;
            return s;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var prog = gl.createProgram();&lt;br /&gt;
        gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT));&lt;br /&gt;
        gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FRAG));&lt;br /&gt;
        gl.linkProgram(prog);&lt;br /&gt;
        gl.useProgram(prog);&lt;br /&gt;
&lt;br /&gt;
        var buf = gl.createBuffer();&lt;br /&gt;
        gl.bindBuffer(gl.ARRAY_BUFFER, buf);&lt;br /&gt;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);&lt;br /&gt;
        var aPos = gl.getAttribLocation(prog, &#039;a_pos&#039;);&lt;br /&gt;
        gl.enableVertexAttribArray(aPos);&lt;br /&gt;
        gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
        var uTex     = gl.getUniformLocation(prog, &#039;u_tex&#039;);&lt;br /&gt;
        var uNoise   = gl.getUniformLocation(prog, &#039;u_noise&#039;);&lt;br /&gt;
        var uRes     = gl.getUniformLocation(prog, &#039;u_res&#039;);&lt;br /&gt;
        var uImgSize = gl.getUniformLocation(prog, &#039;u_imgSize&#039;);&lt;br /&gt;
        var uTime    = gl.getUniformLocation(prog, &#039;u_time&#039;);&lt;br /&gt;
        var uNoiseSc = gl.getUniformLocation(prog, &#039;u_noiseScale&#039;);&lt;br /&gt;
&lt;br /&gt;
        var imgTex = gl.createTexture();&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE1);&lt;br /&gt;
        createNoiseTexture(gl);&lt;br /&gt;
&lt;br /&gt;
        var texReady = false;&lt;br /&gt;
        function uploadImg() {&lt;br /&gt;
            if (!imgEl || !imgEl.complete || !imgEl.naturalWidth) return;&lt;br /&gt;
            try {&lt;br /&gt;
                gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
                gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imgEl);&lt;br /&gt;
                texReady = true;&lt;br /&gt;
            } catch(e) { console.error(&#039;[CRT] tex:&#039;, e); }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var lastW = 0, lastH = 0;&lt;br /&gt;
        function resize() {&lt;br /&gt;
            var w = screen.offsetWidth, h = screen.offsetHeight;&lt;br /&gt;
            if (w === lastW &amp;amp;&amp;amp; h === lastH) return;&lt;br /&gt;
            lastW = w; lastH = h;&lt;br /&gt;
            canvas.width = w; canvas.height = h;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var raf;&lt;br /&gt;
        var t0 = performance.now();&lt;br /&gt;
&lt;br /&gt;
        function render() {&lt;br /&gt;
            raf = requestAnimationFrame(render);&lt;br /&gt;
            if (!texReady) { uploadImg(); return; }&lt;br /&gt;
            resize();&lt;br /&gt;
            var t = (performance.now() - t0) / 1000;&lt;br /&gt;
            gl.uniform1i(uTex, 0);&lt;br /&gt;
            gl.uniform1i(uNoise, 1);&lt;br /&gt;
            gl.uniform2f(uRes, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform2f(uImgSize, imgEl.naturalWidth, imgEl.naturalHeight);&lt;br /&gt;
            gl.uniform1f(uTime, t);&lt;br /&gt;
            gl.uniform2f(uNoiseSc, canvas.width / 512.0, canvas.height / 512.0);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) { uploadImg(); }&lt;br /&gt;
        else { imgEl.addEventListener(&#039;load&#039;, uploadImg); }&lt;br /&gt;
&lt;br /&gt;
        render();&lt;br /&gt;
        screen._crtCleanup = function () { cancelAnimationFrame(raf); };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initAllCRTScreens(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        scope.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (screen) {&lt;br /&gt;
            if (screen.getAttribute(&#039;data-crt-webgl&#039;) === &#039;1&#039;) return;&lt;br /&gt;
            screen.setAttribute(&#039;data-crt-webgl&#039;, &#039;1&#039;);&lt;br /&gt;
            var frame = screen.closest(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
            if (!frame) return;&lt;br /&gt;
            var imgEl = frame.querySelector(&#039;.crt-page-monitor-slice-img, .crt-page-monitor-image-base img&#039;);&lt;br /&gt;
            if (imgEl &amp;amp;&amp;amp; imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) {&lt;br /&gt;
                initCRTCanvas(screen, imgEl);&lt;br /&gt;
            } else if (imgEl) {&lt;br /&gt;
                imgEl.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, imgEl); });&lt;br /&gt;
            } else {&lt;br /&gt;
                var obs = new MutationObserver(function () {&lt;br /&gt;
                    var img = frame.querySelector(&#039;.crt-page-monitor-slice-img&#039;);&lt;br /&gt;
                    if (!img) return;&lt;br /&gt;
                    obs.disconnect();&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        initCRTCanvas(screen, img);&lt;br /&gt;
                    } else {&lt;br /&gt;
                        img.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, img); });&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
                obs.observe(frame, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () { initAllCRTScreens(document); });&lt;br /&gt;
&lt;br /&gt;
    if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($c) {&lt;br /&gt;
            document.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (s) {&lt;br /&gt;
                if (s._crtCleanup) s._crtCleanup();&lt;br /&gt;
                s.removeAttribute(&#039;data-crt-webgl&#039;);&lt;br /&gt;
            });&lt;br /&gt;
            initAllCRTScreens($c &amp;amp;&amp;amp; $c[0] ? $c[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Progress System UI&lt;br /&gt;
MediaWiki:Common.js controlled frontend&lt;br /&gt;
========================================= */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUiInitialized) return;&lt;br /&gt;
    window.ProgressSystemWebUiInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    var api = null;&lt;br /&gt;
&lt;br /&gt;
    function withApi(done, fail) {&lt;br /&gt;
        if (api) {&lt;br /&gt;
            done(api);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
            api = new mw.Api();&lt;br /&gt;
            done(api);&lt;br /&gt;
        }, function () {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var inFlightPageIds = new Set();&lt;br /&gt;
    var handledPageIds = new Set();&lt;br /&gt;
    var notificationQueue = [];&lt;br /&gt;
    var notificationActive = false;&lt;br /&gt;
    var summaryRequested = false;&lt;br /&gt;
    var currentSummary = null;&lt;br /&gt;
    var pendingSummary = null;&lt;br /&gt;
    var pendingOptions = null;&lt;br /&gt;
    var visibilityBound = false;&lt;br /&gt;
    var barTimerA = null;&lt;br /&gt;
    var barTimerB = null;&lt;br /&gt;
    var barTimerC = null;&lt;br /&gt;
    var summaryRetryTimer = null;&lt;br /&gt;
    var summaryRetryAttempts = 0;&lt;br /&gt;
&lt;br /&gt;
    function isLoggedIn() {&lt;br /&gt;
        return !!mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPageId() {&lt;br /&gt;
        var id = parseInt(mw.config.get(&#039;wgArticleId&#039;) || 0, 10);&lt;br /&gt;
        return Number.isFinite(id) ? id : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isRewardableClientSide() {&lt;br /&gt;
        if (!isLoggedIn()) return false;&lt;br /&gt;
        if (parseInt(mw.config.get(&#039;wgNamespaceNumber&#039;), 10) !== 0) return false;&lt;br /&gt;
        if (mw.config.get(&#039;wgIsMainPage&#039;)) return false;&lt;br /&gt;
        if (getPageId() &amp;lt;= 0) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPanelHtml() {&lt;br /&gt;
        return &#039;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;progress-panel&amp;quot; class=&amp;quot;profile-progress-block is-syncing&amp;quot; aria-live=&amp;quot;polite&amp;quot; data-progress-state=&amp;quot;syncing&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-title-row&amp;quot; hidden&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-level-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-level-label&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-gain&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-sub-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-xp-next&amp;quot;&amp;gt;SYNCING&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-daily-xp&amp;quot;&amp;gt;TODAY —&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-discovery-row&amp;quot;&amp;gt;DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDividerHtml() {&lt;br /&gt;
        /* 프로필 패널 최신 규칙: 레벨 패널과 버튼 영역 사이에 별도 나눔선은 만들지 않는다. */&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setPanelSync($panel) {&lt;br /&gt;
        if (!$panel || !$panel.length) return;&lt;br /&gt;
&lt;br /&gt;
        $panel.addClass(&#039;is-syncing&#039;).removeClass(&#039;is-max-level&#039;).attr(&#039;data-progress-state&#039;, &#039;syncing&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-title-row&#039;).text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text(&#039;SYNC&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(&#039;— XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(&#039;SYNCING&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-fill&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
        $panel.find(&#039;.progress-xp-gain&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function placePanel($panel) {&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $userBox = $right.children(&#039;.clbi-right-box&#039;).first();&lt;br /&gt;
        if (!$userBox.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $buttonArea = $userBox.children(&#039;.clbi-right-content&#039;).first();&lt;br /&gt;
        var $oldFallback = $panel.closest(&#039;.progress-panel-fallback&#039;);&lt;br /&gt;
&lt;br /&gt;
        if ($buttonArea.length) {&lt;br /&gt;
            var $divider = $(&#039;#profile-progress-divider&#039;);&lt;br /&gt;
&lt;br /&gt;
            $panel.insertBefore($buttonArea);&lt;br /&gt;
&lt;br /&gt;
            if (!$divider.length) {&lt;br /&gt;
                $divider = $(getDividerHtml());&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $divider.insertAfter($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;#profile-progress-divider&#039;).remove();&lt;br /&gt;
            $userBox.append($panel);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if ($oldFallback.length &amp;amp;&amp;amp; !$oldFallback.find(&#039;#progress-panel&#039;).length) {&lt;br /&gt;
            $oldFallback.remove();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensurePanel() {&lt;br /&gt;
        if (!isLoggedIn()) return $();&lt;br /&gt;
&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return $();&lt;br /&gt;
&lt;br /&gt;
        var $panel = $(&#039;#progress-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            $panel = $(getPanelHtml());&lt;br /&gt;
            if (!placePanel($panel)) return $();&lt;br /&gt;
            setPanelSync($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $panel.addClass(&#039;profile-progress-block&#039;);&lt;br /&gt;
            placePanel($panel);&lt;br /&gt;
&lt;br /&gt;
            if (!currentSummary &amp;amp;&amp;amp; $panel.attr(&#039;data-progress-state&#039;) !== &#039;syncing&#039;) {&lt;br /&gt;
                setPanelSync($panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $(&#039;#progress-panel&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clampPercent(value) {&lt;br /&gt;
        return Math.max(0, Math.min(100, value || 0));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasXpNotification(items) {&lt;br /&gt;
        if (!items || !items.length) return false;&lt;br /&gt;
        return items.some(function (item) {&lt;br /&gt;
            return item &amp;amp;&amp;amp; item.type === &#039;xp&#039; &amp;amp;&amp;amp; parseInt(item.amount || 0, 10) &amp;gt; 0;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearBarTimers() {&lt;br /&gt;
        [barTimerA, barTimerB, barTimerC].forEach(function (timer) {&lt;br /&gt;
            if (timer) clearTimeout(timer);&lt;br /&gt;
        });&lt;br /&gt;
        barTimerA = null;&lt;br /&gt;
        barTimerB = null;&lt;br /&gt;
        barTimerC = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBarInstant($fill, $gain, percent) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
        percent = clampPercent(percent);&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: percent + &#039;%&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;none&#039;, left: &#039;0%&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
        $fill.css({ transition: &#039;&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function animateGain($fill, $gain, fromPercent, toPercent, levelChanged) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
&lt;br /&gt;
        fromPercent = clampPercent(fromPercent);&lt;br /&gt;
        toPercent = clampPercent(toPercent);&lt;br /&gt;
&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: fromPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
        if (levelChanged) {&lt;br /&gt;
            var firstDelta = Math.max(0, 100 - fromPercent);&lt;br /&gt;
&lt;br /&gt;
            $gain.css({&lt;br /&gt;
                transition: &#039;none&#039;,&lt;br /&gt;
                opacity: firstDelta &amp;gt; 0 ? 1 : 0,&lt;br /&gt;
                left: fromPercent + &#039;%&#039;,&lt;br /&gt;
                width: firstDelta + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
            barTimerA = setTimeout(function () {&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 540ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: &#039;100%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 260);&lt;br /&gt;
&lt;br /&gt;
            barTimerB = setTimeout(function () {&lt;br /&gt;
                $fill.css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
                $gain.css({ transition: &#039;none&#039;, opacity: toPercent &amp;gt; 0 ? 1 : 0, left: &#039;0%&#039;, width: toPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
                if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 460ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: toPercent + &#039;%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 860);&lt;br /&gt;
&lt;br /&gt;
            barTimerC = setTimeout(function () {&lt;br /&gt;
                $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
            }, 1380);&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var delta = Math.max(0, toPercent - fromPercent);&lt;br /&gt;
&lt;br /&gt;
        if (delta &amp;lt;= 0.15) {&lt;br /&gt;
            setBarInstant($fill, $gain, toPercent);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $gain.css({&lt;br /&gt;
            transition: &#039;none&#039;,&lt;br /&gt;
            opacity: 1,&lt;br /&gt;
            left: fromPercent + &#039;%&#039;,&lt;br /&gt;
            width: delta + &#039;%&#039;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
        barTimerA = setTimeout(function () {&lt;br /&gt;
            $fill.css({&lt;br /&gt;
                transition: &#039;width 560ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                width: toPercent + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }, 260);&lt;br /&gt;
&lt;br /&gt;
        barTimerB = setTimeout(function () {&lt;br /&gt;
            $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
        }, 940);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updatePanel(summary, options) {&lt;br /&gt;
        if (!summary) return;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
&lt;br /&gt;
        var $panel = ensurePanel();&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            pendingSummary = $.extend({}, summary);&lt;br /&gt;
            pendingOptions = $.extend({}, options);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var level = summary.level || 1;&lt;br /&gt;
        var totalXp = summary.totalXp || 0;&lt;br /&gt;
        var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
        var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
        var percent = clampPercent(summary.progressPercent);&lt;br /&gt;
        var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
        var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
        var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
        var title = summary.equippedTitle || summary.title || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $panel.removeClass(&#039;is-syncing&#039;).toggleClass(&#039;is-max-level&#039;, isMaxLevel).attr(&#039;data-progress-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text((isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(totalXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;));&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY &#039; + dailyXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED &#039; + discoveries);&lt;br /&gt;
&lt;br /&gt;
        var $title = $panel.find(&#039;.progress-title-row&#039;);&lt;br /&gt;
        if (title) {&lt;br /&gt;
            $title.text(title).prop(&#039;hidden&#039;, false);&lt;br /&gt;
        } else {&lt;br /&gt;
            $title.text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $fill = $panel.find(&#039;.progress-xp-fill&#039;);&lt;br /&gt;
        var $gain = $panel.find(&#039;.progress-xp-gain&#039;);&lt;br /&gt;
        var animate = !!options.animateGain &amp;amp;&amp;amp; currentSummary &amp;amp;&amp;amp; totalXp &amp;gt; (currentSummary.totalXp || 0);&lt;br /&gt;
&lt;br /&gt;
        if (animate) {&lt;br /&gt;
            animateGain(&lt;br /&gt;
                $fill,&lt;br /&gt;
                $gain,&lt;br /&gt;
                clampPercent(currentSummary.progressPercent),&lt;br /&gt;
                percent,&lt;br /&gt;
                level !== (currentSummary.level || 1)&lt;br /&gt;
            );&lt;br /&gt;
        } else {&lt;br /&gt;
            setBarInstant($fill, $gain, percent);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        currentSummary = $.extend({}, summary);&lt;br /&gt;
        pendingSummary = null;&lt;br /&gt;
        pendingOptions = null;&lt;br /&gt;
        if (summaryRetryTimer) {&lt;br /&gt;
            clearTimeout(summaryRetryTimer);&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
        }&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearSummaryRetry() {&lt;br /&gt;
        if (summaryRetryTimer) clearTimeout(summaryRetryTimer);&lt;br /&gt;
        summaryRetryTimer = null;&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleSummaryRetry(delay) {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRetryTimer) return;&lt;br /&gt;
        if (summaryRetryAttempts &amp;gt;= 12) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRetryAttempts += 1;&lt;br /&gt;
        summaryRetryTimer = setTimeout(function () {&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, delay || 1800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSummary() {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRequested) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRequested = true;&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;progress_summary&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
                if (payload &amp;amp;&amp;amp; payload.available &amp;amp;&amp;amp; payload.summary) {&lt;br /&gt;
                    clearSummaryRetry();&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: false });&lt;br /&gt;
                } else {&lt;br /&gt;
                    scheduleSummaryRetry(2200);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                scheduleSummaryRetry(2200);&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                summaryRequested = false;&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            summaryRequested = false;&lt;br /&gt;
            scheduleSummaryRetry(2200);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queueNotifications(items) {&lt;br /&gt;
        if (!items || !items.length) return;&lt;br /&gt;
&lt;br /&gt;
        items.forEach(function (item) {&lt;br /&gt;
            if (!item) return;&lt;br /&gt;
            notificationQueue.push(item);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        showNextNotification();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function notificationText(item) {&lt;br /&gt;
        if (item.type === &#039;xp&#039;) {&lt;br /&gt;
            return &#039;+&#039; + (item.amount || 0) + &#039; XP · &#039; + (item.label || &#039;문서 열람&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;achievement&#039;) {&lt;br /&gt;
            var xp = item.amount ? &#039; · +&#039; + item.amount + &#039; XP&#039; : &#039;&#039;;&lt;br /&gt;
            return &#039;업적 달성 · &#039; + (item.label || &#039;새 업적&#039;) + xp;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;level&#039;) {&lt;br /&gt;
            return item.label || &#039;레벨 상승&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return item.label || &#039;보상 획득&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showNextNotification() {&lt;br /&gt;
        if (notificationActive) return;&lt;br /&gt;
        if (!notificationQueue.length) return;&lt;br /&gt;
&lt;br /&gt;
        notificationActive = true;&lt;br /&gt;
        var item = notificationQueue.shift();&lt;br /&gt;
        var $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$root.length) {&lt;br /&gt;
            $(&#039;body&#039;).append(&#039;&amp;lt;div id=&amp;quot;progress-toast-root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $toast = $(&#039;&amp;lt;div class=&amp;quot;progress-toast&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        $toast.text(notificationText(item));&lt;br /&gt;
        $root.append($toast);&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            $toast.addClass(&#039;is-visible&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            $toast.removeClass(&#039;is-visible&#039;);&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                $toast.remove();&lt;br /&gt;
                notificationActive = false;&lt;br /&gt;
                showNextNotification();&lt;br /&gt;
            }, 220);&lt;br /&gt;
        }, 2600);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPendingSummaryIfPossible() {&lt;br /&gt;
        if (!pendingSummary) return;&lt;br /&gt;
        updatePanel(pendingSummary, pendingOptions || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handlePageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (!isRewardableClientSide()) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var pageId = getPageId();&lt;br /&gt;
        if (handledPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (inFlightPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inFlightPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;progress_view&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2,&lt;br /&gt;
                errorformat: &#039;plaintext&#039;,&lt;br /&gt;
                pageid: pageId&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_view;&lt;br /&gt;
                if (!payload) return;&lt;br /&gt;
&lt;br /&gt;
                handledPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
                var animate = hasXpNotification(payload.notifications);&lt;br /&gt;
&lt;br /&gt;
                if (payload.summary) {&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: animate });&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (payload.notifications &amp;amp;&amp;amp; payload.notifications.length) {&lt;br /&gt;
                    queueNotifications(payload.notifications);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                inFlightPageIds.delete(pageId);&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            inFlightPageIds.delete(pageId);&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindVisibilitySync() {&lt;br /&gt;
        if (visibilityBound) return;&lt;br /&gt;
        visibilityBound = true;&lt;br /&gt;
&lt;br /&gt;
        document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
            if (document.visibilityState === &#039;visible&#039;) {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bootProgressSystem(reason) {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (isRewardableClientSide()) {&lt;br /&gt;
            handlePageView();&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 350);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 1500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleSpaPageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                handlePageView();&lt;br /&gt;
            }, 80);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySummary(summary, options) {&lt;br /&gt;
        updatePanel(summary, options || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.ProgressSystemWebUi = {&lt;br /&gt;
        boot: bootProgressSystem,&lt;br /&gt;
        requestSummary: requestSummary,&lt;br /&gt;
        applySummary: applySummary,&lt;br /&gt;
        handlePageView: handlePageView,&lt;br /&gt;
        handleSpaPageView: handleSpaPageView,&lt;br /&gt;
        ensurePanel: ensurePanel&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        bindVisibilitySync();&lt;br /&gt;
        bootProgressSystem(&#039;documentReady&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* CLBI Nations / Historical Events year tabs&lt;br /&gt;
 * Mirrors the country information panel model:&lt;br /&gt;
 * active tab uses .is-active/aria-selected and inactive pages use hidden.&lt;br /&gt;
 */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function activateClbiNationsHistoryYear(panel, targetYear) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var pages;&lt;br /&gt;
&lt;br /&gt;
        if (!panel || !targetYear) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        pages = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-page[data-year-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
        if (!tabs.length || !pages.length) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (tab) {&lt;br /&gt;
            var active = tab.getAttribute(&#039;data-year&#039;) === targetYear;&lt;br /&gt;
            tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
            tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        pages.forEach(function (page) {&lt;br /&gt;
            var active = page.getAttribute(&#039;data-year-panel&#039;) === targetYear;&lt;br /&gt;
            page.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
            if (active) {&lt;br /&gt;
                page.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
            } else {&lt;br /&gt;
                page.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveClbiNationsHistoryYear(panel, direction) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var activeIndex;&lt;br /&gt;
        var nextIndex;&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!panel) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
        activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
            return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
        nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
        target = tabs[nextIndex].getAttribute(&#039;data-year&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (activateClbiNationsHistoryYear(panel, target)) {&lt;br /&gt;
            tabs[nextIndex].focus();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initClbiNationsHistoryYearTabs(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var panels = scope.querySelectorAll(&#039;.clbi-nations-history-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(panels, function (panel) {&lt;br /&gt;
            if (panel.getAttribute(&#039;data-clbi-history-tabs-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
            panel.setAttribute(&#039;data-clbi-history-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (activateClbiNationsHistoryYear(panel, tab.getAttribute(&#039;data-year&#039;))) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
                var handled = false;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsHistoryYear(panel, -1);&lt;br /&gt;
                else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsHistoryYear(panel, 1);&lt;br /&gt;
                else if (event.key === &#039;Home&#039;) handled = activateClbiNationsHistoryYear(panel, (panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;) || {}).getAttribute &amp;amp;&amp;amp; panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;).getAttribute(&#039;data-year&#039;));&lt;br /&gt;
                else if (event.key === &#039;End&#039;) {&lt;br /&gt;
                    var tabs = panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;);&lt;br /&gt;
                    var last = tabs[tabs.length - 1];&lt;br /&gt;
                    handled = last ? activateClbiNationsHistoryYear(panel, last.getAttribute(&#039;data-year&#039;)) : false;&lt;br /&gt;
                    if (handled) last.focus();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (handled) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                    event.stopPropagation();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.initClbiNationsHistoryYearTabs = initClbiNationsHistoryYearTabs;&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initClbiNationsHistoryYearTabs(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initClbiNationsHistoryYearTabs($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Decoration runtime renderer&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var REGISTRY_TITLE = &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
    var RENDERED_ATTR = &#039;data-wiki-decoration-rendered&#039;;&lt;br /&gt;
    var HOST_ATTR = &#039;data-wiki-decoration-host&#039;;&lt;br /&gt;
    var MAIN_PAGE_PLACEMENT = &#039;main-body-well&#039;;&lt;br /&gt;
    var MAIN_PAGE_TARGET = &#039;.main-portal .main-body-well&#039;;&lt;br /&gt;
    var runtimeToken = 0;&lt;br /&gt;
    var lastRegistry = null;&lt;br /&gt;
    var lastRenderedPageKey = &#039;&#039;;&lt;br /&gt;
    var scheduledRender = 0;&lt;br /&gt;
    var nationsPlacementObserver = null;&lt;br /&gt;
    var observedNationsStack = null;&lt;br /&gt;
    var mainPagePlacementObserver = null;&lt;br /&gt;
    var mainPagePlacementMutationObserver = null;&lt;br /&gt;
    var observedMainPagePortal = null;&lt;br /&gt;
    var scheduledMainPageRebase = 0;&lt;br /&gt;
    var pixelAssetCache = {};&lt;br /&gt;
    var pixelCanvasCache = {};&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentPageKey() {&lt;br /&gt;
        var raw = mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        return normalizePageName(raw) || raw || &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureMainPageBodyWell() {&lt;br /&gt;
        var portal;&lt;br /&gt;
        var topMount;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var well;&lt;br /&gt;
        var manifesto;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
        portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        if (!portal) return null;&lt;br /&gt;
&lt;br /&gt;
        topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) {&lt;br /&gt;
                    topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                } else {&lt;br /&gt;
                    topMount.parentNode.appendChild(panel);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) {&lt;br /&gt;
            well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return well;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function cssAttrEscape(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).replace(/\\/g, &#039;\\\\&#039;).replace(/&amp;quot;/g, &#039;\\&amp;quot;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEra() {&lt;br /&gt;
        var content = document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var globe;&lt;br /&gt;
&lt;br /&gt;
        if (content) return content.getAttribute(&#039;data-era-content&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.querySelector(&#039;.clbi-nations-era-title-plate.is-active[data-era]&#039;);&lt;br /&gt;
        if (title) return title.getAttribute(&#039;data-era&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe]&#039;);&lt;br /&gt;
        if (globe) {&lt;br /&gt;
            return globe.getAttribute(&#039;data-current-era&#039;) || globe.getAttribute(&#039;data-nations-current-era&#039;) || globe.getAttribute(&#039;data-era-year&#039;) || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEraPanel() {&lt;br /&gt;
        var era = getActiveNationsEra();&lt;br /&gt;
        var selector;&lt;br /&gt;
        if (!era) return document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        selector = &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;]&#039;;&lt;br /&gt;
        return document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsContinent() {&lt;br /&gt;
        var eraPanel = getActiveNationsEraPanel();&lt;br /&gt;
        var root = eraPanel || document;&lt;br /&gt;
        var tab = root.querySelector(&#039;.clbi-nations-tabpanel-tab.is-active[data-continent], .clbi-nations-tabpanel-tab[aria-selected=&amp;quot;true&amp;quot;][data-continent]&#039;);&lt;br /&gt;
        var panel;&lt;br /&gt;
&lt;br /&gt;
        if (tab) return tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        panel = root.querySelector(&#039;.clbi-nations-tabpanel-continent.is-active[data-continent-panel]&#039;);&lt;br /&gt;
        if (panel) return panel.getAttribute(&#039;data-continent-panel&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationNationsBodySelector(era) {&lt;br /&gt;
        if (era) {&lt;br /&gt;
            return &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;] .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden]) .clbi-nations-tabpanel-body, .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Decoration semantic placement resolver&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    장식 저장 데이터의 placement 값은 &amp;quot;사용자가 고른 의미상 위치&amp;quot;를 나타낸다.&lt;br /&gt;
    resolver는 그 의미값을 실제 DOM 부착 위치와 표시 조건으로 번역한다.&lt;br /&gt;
&lt;br /&gt;
    예: 시대 문서에서 사용자가 1950년 / 아메리카를 지정하면 의미상 scope는&lt;br /&gt;
    그 조합이지만, 이미지를 붙일 기준면은 대륙 패널 자체가 아니라&lt;br /&gt;
    .clbi-nations-tabpanel-body이다. 따라서 placement=nations-continent-body는&lt;br /&gt;
    .clbi-nations-tabpanel-body에 이미지를 붙이고, 현재 활성 연도와 대륙이 저장값과&lt;br /&gt;
    일치할 때만 렌더링한다.&lt;br /&gt;
&lt;br /&gt;
    유지보수 규칙:&lt;br /&gt;
    - 새 조합형 문서가 생기면 entry.target을 매번 특수하게 저장하지 말고 placement를 추가한다.&lt;br /&gt;
    - DevTools.js 에디터 미리보기와 Common.js 런타임 렌더러의 resolver는 같은 의미를 가져야 한다.&lt;br /&gt;
    - target은 물리적 기준면, era/continent 같은 필드는 표시 조건으로 다룬다.&lt;br /&gt;
    */&lt;br /&gt;
    function shouldUseNationsBodyPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var target = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!document.querySelector(&#039;.clbi-nations-panel-stack&#039;)) return false;&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) return true;&lt;br /&gt;
        if (era || continent) return true;&lt;br /&gt;
        if (target.indexOf(&#039;clbi-nations-tabpanel-continent&#039;) !== -1) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveDecorationPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() || &#039;.liberty-content-main&#039;;&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!placement &amp;amp;&amp;amp; shouldUseNationsBodyPlacement(entry)) {&lt;br /&gt;
            placement = &#039;nations-continent-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;boot-gate&#039; || placement === &#039;loading-screen&#039;) {&lt;br /&gt;
            selector = &#039;#boot-gate-screen .boot-gate-decoration-layer, #boot-gate-screen&#039;;&lt;br /&gt;
            target = document.querySelector(selector);&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: !!document.getElementById(&#039;boot-gate-screen&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) {&lt;br /&gt;
            selector = getDecorationNationsBodySelector(era);&lt;br /&gt;
            target = document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-tabpanel-body&#039;) || document.querySelector(String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim());&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: (!era || era === getActiveNationsEra()) &amp;amp;&amp;amp; (!continent || continent === getActiveNationsContinent())&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문의 일반 장식은 저장된 target과 관계없이 본문 우물 안에서 렌더링한다.&lt;br /&gt;
        우물이 overflow:hidden이므로 #1d1d1d 프레임 밖으로 나갈 수 없다.&lt;br /&gt;
        boot/loading 및 국가 패널 전용 placement는 위 분기에서 기존 동작을 유지한다.&lt;br /&gt;
        */&lt;br /&gt;
        var mainPageWell = ensureMainPageBodyWell();&lt;br /&gt;
        if (mainPageWell) {&lt;br /&gt;
            return {&lt;br /&gt;
                placement: MAIN_PAGE_PLACEMENT,&lt;br /&gt;
                target: mainPageWell,&lt;br /&gt;
                targetSelector: MAIN_PAGE_TARGET,&lt;br /&gt;
                visible: true&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            placement: placement,&lt;br /&gt;
            target: document.querySelector(selector),&lt;br /&gt;
            targetSelector: selector,&lt;br /&gt;
            visible: true&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeNumber(value, fallback) {&lt;br /&gt;
        var n = parseFloat(value);&lt;br /&gt;
        return Number.isFinite(n) ? n : fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBool(value, fallback) {&lt;br /&gt;
        if (value === true || value === &#039;true&#039; || value === &#039;1&#039; || value === 1) return true;&lt;br /&gt;
        if (value === false || value === &#039;false&#039; || value === &#039;0&#039; || value === 0) return false;&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeSrc(src) {&lt;br /&gt;
        src = String(src || &#039;&#039;).trim();&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(src) || src.charAt(0) === &#039;/&#039; || src.indexOf(&#039;data:&#039;) === 0 || src.indexOf(&#039;blob:&#039;) === 0) return src;&lt;br /&gt;
        return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(src);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function normalizeAssetType(entry) {&lt;br /&gt;
        var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var ref = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (type === &#039;clbi-pixel-json&#039; || type === &#039;pixel-rle&#039; || type === &#039;pixel-json&#039;) return &#039;pixel-json&#039;;&lt;br /&gt;
        if (!type &amp;amp;&amp;amp; /\.json(?:[?#].*)?$/i.test(ref)) return &#039;pixel-json&#039;;&lt;br /&gt;
        return type || &#039;image&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPixelJsonDecoration(entry) {&lt;br /&gt;
        return normalizeAssetType(entry) === &#039;pixel-json&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPixelJsonRef(entry) {&lt;br /&gt;
        return String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelJsonUrl(ref) {&lt;br /&gt;
        ref = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        if (!ref) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(ref) || ref.charAt(0) === &#039;/&#039; || ref.indexOf(&#039;data:&#039;) === 0 || ref.indexOf(&#039;blob:&#039;) === 0) return ref;&lt;br /&gt;
        if (/^(?:file|파일):/i.test(ref)) return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(ref.replace(/^(?:file|파일):/i, &#039;&#039;));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            if (ref.indexOf(&#039;:&#039;) !== -1) {&lt;br /&gt;
                return mw.util.getUrl(ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
            }&lt;br /&gt;
            return mw.util.getUrl(&#039;MediaWiki:&#039; + ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return ref;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function parsePixelColor(value) {&lt;br /&gt;
        var text;&lt;br /&gt;
        var m;&lt;br /&gt;
        if (Array.isArray(value)) {&lt;br /&gt;
            return [&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[0]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[1]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[2]) || 0)),&lt;br /&gt;
                value.length &amp;gt; 3 ? Math.max(0, Math.min(255, Number(value[3]) || 0)) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        m = text.match(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i);&lt;br /&gt;
        if (!m) return [0, 0, 0, 0];&lt;br /&gt;
        text = m[1];&lt;br /&gt;
        if (text.length === 3 || text.length === 4) {&lt;br /&gt;
            return [&lt;br /&gt;
                parseInt(text.charAt(0) + text.charAt(0), 16),&lt;br /&gt;
                parseInt(text.charAt(1) + text.charAt(1), 16),&lt;br /&gt;
                parseInt(text.charAt(2) + text.charAt(2), 16),&lt;br /&gt;
                text.length === 4 ? parseInt(text.charAt(3) + text.charAt(3), 16) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        return [&lt;br /&gt;
            parseInt(text.slice(0, 2), 16),&lt;br /&gt;
            parseInt(text.slice(2, 4), 16),&lt;br /&gt;
            parseInt(text.slice(4, 6), 16),&lt;br /&gt;
            text.length === 8 ? parseInt(text.slice(6, 8), 16) : 255&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function decodePixelRle36(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var parts;&lt;br /&gt;
        var runs = [];&lt;br /&gt;
        var i;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
&lt;br /&gt;
        if (!text) return runs;&lt;br /&gt;
        parts = text.split(&#039;,&#039;);&lt;br /&gt;
        for (i = 0; i + 3 &amp;lt; parts.length; i += 4) {&lt;br /&gt;
            x = parseInt(parts[i], 36);&lt;br /&gt;
            y = parseInt(parts[i + 1], 36);&lt;br /&gt;
            len = parseInt(parts[i + 2], 36);&lt;br /&gt;
            colorIndex = parseInt(parts[i + 3], 36);&lt;br /&gt;
            if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(len) || !Number.isFinite(colorIndex)) continue;&lt;br /&gt;
            runs.push([x, y, len, colorIndex]);&lt;br /&gt;
        }&lt;br /&gt;
        return runs;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelAsset(doc) {&lt;br /&gt;
        var encoding = String(doc &amp;amp;&amp;amp; (doc.encoding || doc.e) || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var width = Math.round(Number(doc &amp;amp;&amp;amp; (doc.width || doc.w)) || 0);&lt;br /&gt;
        var height = Math.round(Number(doc &amp;amp;&amp;amp; (doc.height || doc.h)) || 0);&lt;br /&gt;
        var paletteSource = Array.isArray(doc &amp;amp;&amp;amp; doc.palette) ? doc.palette : (Array.isArray(doc &amp;amp;&amp;amp; doc.p) ? doc.p : []);&lt;br /&gt;
        var palette = paletteSource.map(parsePixelColor);&lt;br /&gt;
        var runs;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        CLBI Pixel Forge v0.2.3 compact format:&lt;br /&gt;
        - MediaWiki 단일 문서 크기 제한을 피하기 위해 사람이 읽기 쉬운 [[x,y,len,c], ...] 배열 대신&lt;br /&gt;
          base36 토큰 문자열을 쓴다.&lt;br /&gt;
        - 형식은 x,y,len,colorIndex를 4개 토큰 단위로 반복한 rle36이다.&lt;br /&gt;
        - 복원 후 좌표계는 기존 runs 배열과 완전히 같으며, 최종 CSS px에 1:1로 찍는다.&lt;br /&gt;
        */&lt;br /&gt;
        if ((encoding === &#039;rle36&#039; || encoding === &#039;clbi-rle36&#039;) &amp;amp;&amp;amp; typeof (doc &amp;amp;&amp;amp; doc.r) === &#039;string&#039;) {&lt;br /&gt;
            runs = decodePixelRle36(doc.r);&lt;br /&gt;
        } else {&lt;br /&gt;
            runs = Array.isArray(doc &amp;amp;&amp;amp; doc.runs) ? doc.runs : [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!width || !height || width &amp;lt; 1 || height &amp;lt; 1) throw new Error(&#039;invalid pixel decoration size&#039;);&lt;br /&gt;
        if (width &amp;gt; 8192 || height &amp;gt; 8192) throw new Error(&#039;pixel decoration too large&#039;);&lt;br /&gt;
        return {&lt;br /&gt;
            type: String(doc &amp;amp;&amp;amp; (doc.type || doc.t) || &#039;clbi-pixel-decoration&#039;),&lt;br /&gt;
            version: Number(doc &amp;amp;&amp;amp; (doc.version || doc.v)) || 1,&lt;br /&gt;
            encoding: encoding || &#039;runs&#039;,&lt;br /&gt;
            width: width,&lt;br /&gt;
            height: height,&lt;br /&gt;
            palette: palette,&lt;br /&gt;
            runs: runs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchPixelAsset(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelAssetCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {&lt;br /&gt;
            promise: fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
                .then(function (response) {&lt;br /&gt;
                    if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                    return response.json();&lt;br /&gt;
                })&lt;br /&gt;
                .then(normalizePixelAsset)&lt;br /&gt;
        };&lt;br /&gt;
        pixelAssetCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawPixelAssetToCanvas(canvas, asset) {&lt;br /&gt;
        var ctx;&lt;br /&gt;
        var image;&lt;br /&gt;
        var data;&lt;br /&gt;
        var i;&lt;br /&gt;
        var run;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var color;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
        var p;&lt;br /&gt;
        var k;&lt;br /&gt;
&lt;br /&gt;
        canvas.width = asset.width;&lt;br /&gt;
        canvas.height = asset.height;&lt;br /&gt;
        ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        ctx.imageSmoothingEnabled = false;&lt;br /&gt;
        image = ctx.createImageData(asset.width, asset.height);&lt;br /&gt;
        data = image.data;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; asset.runs.length; i += 1) {&lt;br /&gt;
            run = asset.runs[i];&lt;br /&gt;
            if (!Array.isArray(run) || run.length &amp;lt; 4) continue;&lt;br /&gt;
            x = Math.round(Number(run[0]) || 0);&lt;br /&gt;
            y = Math.round(Number(run[1]) || 0);&lt;br /&gt;
            len = Math.round(Number(run[2]) || 0);&lt;br /&gt;
            colorIndex = Math.round(Number(run[3]) || 0);&lt;br /&gt;
            color = asset.palette[colorIndex];&lt;br /&gt;
            if (!color || len &amp;lt;= 0 || y &amp;lt; 0 || y &amp;gt;= asset.height || x &amp;gt;= asset.width) continue;&lt;br /&gt;
            if (x &amp;lt; 0) {&lt;br /&gt;
                len += x;&lt;br /&gt;
                x = 0;&lt;br /&gt;
            }&lt;br /&gt;
            len = Math.min(len, asset.width - x);&lt;br /&gt;
            for (k = 0; k &amp;lt; len; k += 1) {&lt;br /&gt;
                p = ((y * asset.width) + x + k) * 4;&lt;br /&gt;
                data[p] = color[0];&lt;br /&gt;
                data[p + 1] = color[1];&lt;br /&gt;
                data[p + 2] = color[2];&lt;br /&gt;
                data[p + 3] = color[3];&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preparePixelCanvas(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelCanvasCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {};&lt;br /&gt;
        cached.promise = fetchPixelAsset(ref).then(function (asset) {&lt;br /&gt;
            var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            drawPixelAssetToCanvas(canvas, asset);&lt;br /&gt;
            canvas.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;data-decoration-asset-size&#039;, asset.width + &#039;x&#039; + asset.height);&lt;br /&gt;
            cached.canvas = canvas;&lt;br /&gt;
            cached.width = asset.width;&lt;br /&gt;
            cached.height = asset.height;&lt;br /&gt;
            return canvas;&lt;br /&gt;
        });&lt;br /&gt;
        pixelCanvasCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedPixelCanvasSync(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached = url ? pixelCanvasCache[url] : null;&lt;br /&gt;
        return cached &amp;amp;&amp;amp; cached.canvas ? cached.canvas : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queryDecorationTarget(selector) {&lt;br /&gt;
        selector = String(selector || &#039;&#039;).trim();&lt;br /&gt;
        if (!selector) return null;&lt;br /&gt;
        try {&lt;br /&gt;
            return document.querySelector(selector);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function usesCanonicalMainPageCoordinates(entry, actualTarget) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var declaredTarget;&lt;br /&gt;
&lt;br /&gt;
        if (placement === MAIN_PAGE_PLACEMENT) return true;&lt;br /&gt;
        if (!actualTarget || !selector) return false;&lt;br /&gt;
&lt;br /&gt;
        declaredTarget = queryDecorationTarget(selector);&lt;br /&gt;
        return declaredTarget === actualTarget;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationLocalPosition(entry, actualTarget) {&lt;br /&gt;
        var x = normalizeNumber(entry &amp;amp;&amp;amp; entry.x, 0);&lt;br /&gt;
        var y = normalizeNumber(entry &amp;amp;&amp;amp; entry.y, 0);&lt;br /&gt;
        var sourceX = x;&lt;br /&gt;
        var sourceY = y;&lt;br /&gt;
        var sourceSelector;&lt;br /&gt;
        var sourceTarget;&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var actualRect;&lt;br /&gt;
        var sourceOriginX;&lt;br /&gt;
        var sourceOriginY;&lt;br /&gt;
        var actualOriginX;&lt;br /&gt;
        var actualOriginY;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문 데코 좌표 보존&lt;br /&gt;
        -----------------------------------------&lt;br /&gt;
        저장된 x/y는 기존 entry.target 좌표계를 기준으로 만들어졌다.&lt;br /&gt;
        데코를 main-body-well 안으로 옮길 때 x/y를 그대로 적용하면&lt;br /&gt;
        더 아래에서 시작하는 새 좌표계 때문에 장식이 바닥으로 밀린다.&lt;br /&gt;
&lt;br /&gt;
        실제 화면상의 위치는 유지하고 부모만 우물로 바꾼다.&lt;br /&gt;
        기존 기준면의 padding-box 원점과 새 우물의 padding-box 원점 차이를&lt;br /&gt;
        정수 px로 더해 새 로컬 좌표를 만든다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            currentPageKey() === &#039;대문&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList.contains(&#039;main-body-well&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
            !usesCanonicalMainPageCoordinates(entry, actualTarget)&lt;br /&gt;
        ) {&lt;br /&gt;
            sourceSelector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() ||&lt;br /&gt;
                &#039;.liberty-content-main&#039;;&lt;br /&gt;
            sourceTarget = queryDecorationTarget(sourceSelector) ||&lt;br /&gt;
                document.querySelector(&#039;.liberty-content-main&#039;) ||&lt;br /&gt;
                document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (sourceTarget &amp;amp;&amp;amp; sourceTarget !== actualTarget) {&lt;br /&gt;
                sourceRect = sourceTarget.getBoundingClientRect();&lt;br /&gt;
                actualRect = actualTarget.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
                sourceOriginX = sourceRect.left + normalizeNumber(sourceTarget.clientLeft, 0);&lt;br /&gt;
                sourceOriginY = sourceRect.top + normalizeNumber(sourceTarget.clientTop, 0);&lt;br /&gt;
                actualOriginX = actualRect.left + normalizeNumber(actualTarget.clientLeft, 0);&lt;br /&gt;
                actualOriginY = actualRect.top + normalizeNumber(actualTarget.clientTop, 0);&lt;br /&gt;
&lt;br /&gt;
                x += Math.round(sourceOriginX - actualOriginX);&lt;br /&gt;
                y += Math.round(sourceOriginY - actualOriginY);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            x: Math.round(x),&lt;br /&gt;
            y: Math.round(y),&lt;br /&gt;
            sourceX: Math.round(sourceX),&lt;br /&gt;
            sourceY: Math.round(sourceY),&lt;br /&gt;
            sourceSelector: sourceSelector || &#039;&#039;&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecorationBaseStyle(node, entry, actualTarget) {&lt;br /&gt;
        var position = getDecorationLocalPosition(entry, actualTarget);&lt;br /&gt;
&lt;br /&gt;
        node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
        node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
        node.style.opacity = String(normalizeNumber(entry.opacity, 1));&lt;br /&gt;
        node.style.zIndex = String(Math.round(normalizeNumber(entry.zIndex, 0)));&lt;br /&gt;
        node.style.pointerEvents = String(entry.pointerEvents || &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (position.sourceSelector) {&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-coordinate-source&#039;, position.sourceSelector);&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-x&#039;, String(position.sourceX));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-y&#039;, String(position.sourceY));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (entry.blendMode) node.style.mixBlendMode = String(entry.blendMode);&lt;br /&gt;
        if (entry.filter) node.style.filter = String(entry.filter);&lt;br /&gt;
        if (entry.transform) node.style.transform = String(entry.transform);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDecorationNodeActiveForNationsState(node) {&lt;br /&gt;
        var era = node ? String(node.getAttribute(&#039;data-decoration-era&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        var continent = node ? String(node.getAttribute(&#039;data-decoration-continent&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; era !== getActiveNationsEra()) return false;&lt;br /&gt;
        if (continent &amp;amp;&amp;amp; continent !== getActiveNationsContinent()) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDecorationNodeVisibility(node, visible) {&lt;br /&gt;
        if (!node) return;&lt;br /&gt;
        visible = visible !== false;&lt;br /&gt;
        /* aria-hidden stays true because wiki decorations are purely visual. */&lt;br /&gt;
        if (node.hidden !== !visible) node.hidden = !visible;&lt;br /&gt;
        if (node.style.display !== (visible ? &#039;&#039; : &#039;none&#039;)) node.style.display = visible ? &#039;&#039; : &#039;none&#039;;&lt;br /&gt;
        node.setAttribute(&#039;data-decoration-visible&#039;, visible ? &#039;1&#039; : &#039;0&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateDecorationVisibility(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var count = 0;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            setDecorationNodeVisibility(node, isDecorationNodeActiveForNationsState(node));&lt;br /&gt;
            count += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return count;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasRenderedDecorations(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return !!(scope &amp;amp;&amp;amp; scope.querySelector &amp;amp;&amp;amp; scope.querySelector(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPixelJsonDecoration(entry, target, visible) {&lt;br /&gt;
        var ref = getPixelJsonRef(entry);&lt;br /&gt;
        var template;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var ctx;&lt;br /&gt;
        if (!ref) return false;&lt;br /&gt;
&lt;br /&gt;
        function makeCanvasFromTemplate(source) {&lt;br /&gt;
            var out = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            var outCtx;&lt;br /&gt;
            out.className = &#039;wiki-decoration wiki-decoration-pixel-json&#039;;&lt;br /&gt;
            out.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
            out.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-type&#039;, &#039;pixel-json&#039;);&lt;br /&gt;
            if (entry.placement) out.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
            if (entry.era) out.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
            if (entry.continent) out.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
            out.width = source.width;&lt;br /&gt;
            out.height = source.height;&lt;br /&gt;
            out.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            out.style.width = source.width + &#039;px&#039;;&lt;br /&gt;
            out.style.height = source.height + &#039;px&#039;;&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-size&#039;, source.width + &#039;x&#039; + source.height);&lt;br /&gt;
            outCtx = out.getContext(&#039;2d&#039;);&lt;br /&gt;
            outCtx.imageSmoothingEnabled = false;&lt;br /&gt;
            outCtx.drawImage(source, 0, 0);&lt;br /&gt;
            applyDecorationBaseStyle(out, entry, target);&lt;br /&gt;
            setDecorationNodeVisibility(out, visible);&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template = getPreparedPixelCanvasSync(ref);&lt;br /&gt;
        if (template) {&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(template));&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Fallback path only.  A full entry pack should prepare the template before the&lt;br /&gt;
           normal UI is released, so users should not see a blank decoration canvas. */&lt;br /&gt;
        preparePixelCanvas(ref).then(function (source) {&lt;br /&gt;
            if (!target || !target.parentNode || !source) return;&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(source));&lt;br /&gt;
        }).catch(function () {});&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preloadPixelAssetsForRegistry(registry) {&lt;br /&gt;
        var promises = [];&lt;br /&gt;
        decorationList(registry).forEach(function (entry) {&lt;br /&gt;
            var ref;&lt;br /&gt;
            if (!entry || !matchesPage(entry) || !isPixelJsonDecoration(entry)) return;&lt;br /&gt;
            ref = getPixelJsonRef(entry);&lt;br /&gt;
            if (!ref) return;&lt;br /&gt;
            promises.push(preparePixelCanvas(ref).catch(function () { return null; }));&lt;br /&gt;
        });&lt;br /&gt;
        return Promise.all(promises);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decorationList(registry) {&lt;br /&gt;
        if (!registry || typeof registry !== &#039;object&#039;) return [];&lt;br /&gt;
        if (Array.isArray(registry)) return registry;&lt;br /&gt;
        if (Array.isArray(registry.decorations)) return registry.decorations;&lt;br /&gt;
        return [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesPage(entry) {&lt;br /&gt;
        var page = currentPageKey();&lt;br /&gt;
        var underscored = page.replace(/ /g, &#039;_&#039;);&lt;br /&gt;
        var pages = entry &amp;amp;&amp;amp; entry.pages;&lt;br /&gt;
        var target = entry &amp;amp;&amp;amp; entry.page;&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        if (normalizeBool(entry &amp;amp;&amp;amp; entry.global, false)) return true;&lt;br /&gt;
        if (String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;boot-gate&#039; || String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;loading-screen&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (normalizePageName(target).toLowerCase() === &#039;__boot__&#039; || normalizePageName(target).toLowerCase() === &#039;loading-screen&#039;) return true;&lt;br /&gt;
        if (!target &amp;amp;&amp;amp; !pages) return true;&lt;br /&gt;
&lt;br /&gt;
        if (Array.isArray(pages)) {&lt;br /&gt;
            for (i = 0; i &amp;lt; pages.length; i += 1) {&lt;br /&gt;
                if (normalizePageName(pages[i]) === page || String(pages[i] || &#039;&#039;) === underscored) return true;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        target = normalizePageName(target);&lt;br /&gt;
        return target === page || target === underscored;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearRendered(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureHost(target) {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (!target) return;&lt;br /&gt;
        target.setAttribute(HOST_ATTR, &#039;1&#039;);&lt;br /&gt;
        style = window.getComputedStyle ? window.getComputedStyle(target) : null;&lt;br /&gt;
        if (style &amp;amp;&amp;amp; style.position === &#039;static&#039;) target.style.position = &#039;relative&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecoration(entry) {&lt;br /&gt;
        var resolved;&lt;br /&gt;
        var target;&lt;br /&gt;
        var src;&lt;br /&gt;
        var img;&lt;br /&gt;
        var width;&lt;br /&gt;
        var height;&lt;br /&gt;
&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039; || normalizeBool(entry.enabled, true) === false) return false;&lt;br /&gt;
        resolved = resolveDecorationPlacement(entry);&lt;br /&gt;
        if (!resolved) return false;&lt;br /&gt;
        target = resolved.target;&lt;br /&gt;
        if (!target) return false;&lt;br /&gt;
&lt;br /&gt;
        ensureHost(target);&lt;br /&gt;
&lt;br /&gt;
        if (isPixelJsonDecoration(entry)) {&lt;br /&gt;
            return applyPixelJsonDecoration(entry, target, resolved.visible !== false);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        src = normalizeSrc(entry.src);&lt;br /&gt;
        if (!src) return false;&lt;br /&gt;
&lt;br /&gt;
        img = document.createElement(&#039;img&#039;);&lt;br /&gt;
        img.className = &#039;wiki-decoration&#039;;&lt;br /&gt;
        img.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
        img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, &#039;&#039;);&lt;br /&gt;
        img.setAttribute(&#039;decoding&#039;, &#039;async&#039;);&lt;br /&gt;
        img.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        img.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
        if (entry.placement) img.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
        if (entry.era) img.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
        if (entry.continent) img.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
        img.src = src;&lt;br /&gt;
&lt;br /&gt;
        applyDecorationBaseStyle(img, entry, target);&lt;br /&gt;
        width = normalizeNumber(entry.width, NaN);&lt;br /&gt;
        height = normalizeNumber(entry.height, NaN);&lt;br /&gt;
        if (Number.isFinite(width) &amp;amp;&amp;amp; width &amp;gt; 0) img.style.width = width + &#039;px&#039;;&lt;br /&gt;
        if (Number.isFinite(height) &amp;amp;&amp;amp; height &amp;gt; 0) img.style.height = height + &#039;px&#039;;&lt;br /&gt;
        if (entry.objectFit) img.style.objectFit = String(entry.objectFit);&lt;br /&gt;
        setDecorationNodeVisibility(img, resolved.visible !== false);&lt;br /&gt;
&lt;br /&gt;
        target.appendChild(img);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mainPageLayoutSignature() {&lt;br /&gt;
        var source = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
        var well = ensureMainPageBodyWell();&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var wellRect;&lt;br /&gt;
&lt;br /&gt;
        if (!source || !well) return &#039;&#039;;&lt;br /&gt;
        sourceRect = source.getBoundingClientRect();&lt;br /&gt;
        wellRect = well.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            Math.round((wellRect.left + normalizeNumber(well.clientLeft, 0)) - (sourceRect.left + normalizeNumber(source.clientLeft, 0))),&lt;br /&gt;
            Math.round((wellRect.top + normalizeNumber(well.clientTop, 0)) - (sourceRect.top + normalizeNumber(source.clientTop, 0))),&lt;br /&gt;
            Math.round(wellRect.width),&lt;br /&gt;
            Math.round(wellRect.height)&lt;br /&gt;
        ].join(&#039;|&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMainPageDecorationLayoutReady() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var panel = portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var topMount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!portal || !panel || !ensureMainPageBodyWell()) return false;&lt;br /&gt;
        if (!document.body || !document.body.classList.contains(&#039;clbi-main-page&#039;)) return false;&lt;br /&gt;
        if (topMount &amp;amp;&amp;amp; !topMount.querySelector(&#039;.portal-category-nav&#039;)) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForMainPageDecorationLayout() {&lt;br /&gt;
        var started;&lt;br /&gt;
        var previous = &#039;&#039;;&lt;br /&gt;
        var stableFrames = 0;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return Promise.resolve(true);&lt;br /&gt;
        started = Date.now();&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function check() {&lt;br /&gt;
                var signature = mainPageLayoutSignature();&lt;br /&gt;
&lt;br /&gt;
                if (signature &amp;amp;&amp;amp; signature === previous) stableFrames += 1;&lt;br /&gt;
                else stableFrames = 0;&lt;br /&gt;
                previous = signature;&lt;br /&gt;
&lt;br /&gt;
                if ((isMainPageDecorationLayoutReady() &amp;amp;&amp;amp; stableFrames &amp;gt;= 2) || Date.now() - started &amp;gt;= 1200) {&lt;br /&gt;
                    resolve(true);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (window.requestAnimationFrame) window.requestAnimationFrame(check);&lt;br /&gt;
                else window.setTimeout(check, 16);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebaseLegacyMainPageDecorations() {&lt;br /&gt;
        var well;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return;&lt;br /&gt;
        well = ensureMainPageBodyWell();&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-coordinate-source]&#039;).forEach(function (node) {&lt;br /&gt;
            var position = getDecorationLocalPosition({&lt;br /&gt;
                target: node.getAttribute(&#039;data-decoration-coordinate-source&#039;) || &#039;.liberty-content-main&#039;,&lt;br /&gt;
                x: normalizeNumber(node.getAttribute(&#039;data-decoration-source-x&#039;), 0),&lt;br /&gt;
                y: normalizeNumber(node.getAttribute(&#039;data-decoration-source-y&#039;), 0)&lt;br /&gt;
            }, well);&lt;br /&gt;
&lt;br /&gt;
            node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
            node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleMainPageDecorationRebase() {&lt;br /&gt;
        if (scheduledMainPageRebase) return;&lt;br /&gt;
        scheduledMainPageRebase = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindMainPagePlacementRefresh() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var nodes;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039; || !portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = null;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (observedMainPagePortal !== portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = portal;&lt;br /&gt;
&lt;br /&gt;
            if (typeof ResizeObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementObserver = new ResizeObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                nodes = [&lt;br /&gt;
                    document.querySelector(&#039;.liberty-content-main&#039;),&lt;br /&gt;
                    portal,&lt;br /&gt;
                    portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-panel&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-well&#039;)&lt;br /&gt;
                ];&lt;br /&gt;
                nodes.forEach(function (node) {&lt;br /&gt;
                    if (node) mainPagePlacementObserver.observe(node);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementMutationObserver = new MutationObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                mainPagePlacementMutationObserver.observe(portal, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        scheduleMainPageDecorationRebase();&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 80);&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderForCurrentLayout(registry, token) {&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return Promise.resolve(registry);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return waitForMainPageDecorationLayout().then(function () {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return registry;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(registry) {&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        lastRegistry = registry || { decorations: [] };&lt;br /&gt;
        lastRenderedPageKey = currentPageKey();&lt;br /&gt;
        bindNationsPlacementRefresh();&lt;br /&gt;
        clearRendered(document);&lt;br /&gt;
        decorationList(lastRegistry).forEach(function (entry) {&lt;br /&gt;
            if (token === runtimeToken &amp;amp;&amp;amp; matchesPage(entry)) applyDecoration(entry);&lt;br /&gt;
        });&lt;br /&gt;
        bindMainPagePlacementRefresh();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPrepared() {&lt;br /&gt;
        var registry = getPreparedRegistrySync() || lastRegistry;&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        if (!registry) return false;&lt;br /&gt;
        renderForCurrentLayout(registry, token);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function syncDecorationState() {&lt;br /&gt;
        if (lastRenderedPageKey === currentPageKey() &amp;amp;&amp;amp; hasRenderedDecorations(document)) {&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (lastRegistry) {&lt;br /&gt;
            renderForCurrentLayout(lastRegistry, runtimeToken);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        return renderPrepared();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleRenderFromCache() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleDecorationVisibilityUpdate() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindNationsPlacementRefresh() {&lt;br /&gt;
        var stack = document.querySelector(&#039;.clbi-nations-panel-stack&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!stack || observedNationsStack === stack) return;&lt;br /&gt;
        observedNationsStack = stack;&lt;br /&gt;
&lt;br /&gt;
        if (nationsPlacementObserver) {&lt;br /&gt;
            nationsPlacementObserver.disconnect();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
            nationsPlacementObserver = new MutationObserver(scheduleDecorationVisibilityUpdate);&lt;br /&gt;
            nationsPlacementObserver.observe(stack, {&lt;br /&gt;
                subtree: true,&lt;br /&gt;
                attributes: true,&lt;br /&gt;
                attributeFilter: [&#039;class&#039;, &#039;hidden&#039;, &#039;aria-selected&#039;, &#039;data-current-era&#039;, &#039;data-nations-current-era&#039;, &#039;data-era-year&#039;]&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedRegistrySync() {&lt;br /&gt;
        var registry = null;&lt;br /&gt;
        var url;&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.getJsonSync === &#039;function&#039;) {&lt;br /&gt;
                registry = window.EntryStore.getJsonSync(REGISTRY_TITLE);&lt;br /&gt;
                if (!registry &amp;amp;&amp;amp; mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                    url = mw.util.getUrl(REGISTRY_TITLE, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
                    registry = window.EntryStore.getJsonSync(url);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return registry &amp;amp;&amp;amp; typeof registry === &#039;object&#039; ? registry : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchRegistry() {&lt;br /&gt;
        var url;&lt;br /&gt;
        var prepared = getPreparedRegistrySync();&lt;br /&gt;
        if (prepared) return Promise.resolve(prepared);&lt;br /&gt;
        if (!mw || !mw.util || typeof fetch !== &#039;function&#039;) return Promise.resolve({ decorations: [] });&lt;br /&gt;
        url = mw.util.getUrl(REGISTRY_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
            .then(function (response) {&lt;br /&gt;
                if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                return response.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (!text || !text.trim()) return { decorations: [] };&lt;br /&gt;
                return JSON.parse(text);&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                return { decorations: [] };&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function reload() {&lt;br /&gt;
        var token;&lt;br /&gt;
        runtimeToken += 1;&lt;br /&gt;
        token = runtimeToken;&lt;br /&gt;
        return fetchRegistry().then(function (registry) {&lt;br /&gt;
            return preloadPixelAssetsForRegistry(registry).then(function () {&lt;br /&gt;
                return renderForCurrentLayout(registry, token);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
        var target = event.target &amp;amp;&amp;amp; event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent], [data-nations-era-move]&#039;) : null;&lt;br /&gt;
        if (target) window.setTimeout(scheduleDecorationVisibilityUpdate, 0);&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    function decorationDiagnostics() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: &#039;20260713-main-body-well-coordinate-003&#039;,&lt;br /&gt;
            hasRegistry: !!lastRegistry,&lt;br /&gt;
            entries: decorationList(lastRegistry).length,&lt;br /&gt;
            rendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            preparedRegistry: !!getPreparedRegistrySync(),&lt;br /&gt;
            pixelAssets: Object.keys(pixelAssetCache || {}).length,&lt;br /&gt;
            pixelCanvases: Object.keys(pixelCanvasCache || {}).length,&lt;br /&gt;
            visibilityOnlySync: true,&lt;br /&gt;
            mainPageCoordinateSurface: MAIN_PAGE_TARGET,&lt;br /&gt;
            mainPagePlacementObserved: !!observedMainPagePortal,&lt;br /&gt;
            lastRenderedPage: lastRenderedPageKey,&lt;br /&gt;
            visibleRendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-visible=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            scheduled: !!scheduledRender&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.Decorations = window.Decorations || {};&lt;br /&gt;
    window.Decorations.reload = reload;&lt;br /&gt;
    window.Decorations.render = render;&lt;br /&gt;
    window.Decorations.renderPrepared = renderPrepared;&lt;br /&gt;
    window.Decorations.sync = syncDecorationState;&lt;br /&gt;
    window.Decorations.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.Decorations.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.Decorations.clear = clearRendered;&lt;br /&gt;
    window.Decorations.apply = applyDecoration;&lt;br /&gt;
    window.Decorations.pageKey = currentPageKey;&lt;br /&gt;
    window.Decorations.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.Decorations.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.Decorations.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.Decorations.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_DECORATIONS = window.CLBI_DECORATIONS || {};&lt;br /&gt;
    window.CLBI_DECORATIONS.reload = reload;&lt;br /&gt;
    window.CLBI_DECORATIONS.render = render;&lt;br /&gt;
    window.CLBI_DECORATIONS.renderPrepared = renderPrepared;&lt;br /&gt;
    window.CLBI_DECORATIONS.sync = syncDecorationState;&lt;br /&gt;
    window.CLBI_DECORATIONS.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.CLBI_DECORATIONS.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.CLBI_DECORATIONS.clear = clearRendered;&lt;br /&gt;
    window.CLBI_DECORATIONS.apply = applyDecoration;&lt;br /&gt;
    window.CLBI_DECORATIONS.pageKey = currentPageKey;&lt;br /&gt;
    window.CLBI_DECORATIONS.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.CLBI_DECORATIONS.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.CLBI_DECORATIONS.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.CLBI_DECORATIONS.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, reload);&lt;br /&gt;
    } else {&lt;br /&gt;
        reload();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            reload();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Unified Shortcuts loader&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!window.mw || !mw.loader) return;&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Nation_List_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:History_Event_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Shortcuts.js&#039;);&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Main page manifesto bitmap renderer&lt;br /&gt;
   Integrated build: no extra raw-script page, no image/data-URL decoding.&lt;br /&gt;
   New shared systems do not use the legacy CLBI prefix.&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Retired compatibility block&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    선언문은 실제 HTML과 ManifestoIntro의 실선 요소만 사용한다.&lt;br /&gt;
    이 구식 렌더러는 제목·본문과 완성형 구분선을 canvas에 한 번에 그려&lt;br /&gt;
    새 애니메이션의 선 뒤에 트랙이 나타날 수 있었다.&lt;br /&gt;
&lt;br /&gt;
    기존 Common.js를 장기 캐시한 뒤 새 파일로 넘어오는 경우를 위해&lt;br /&gt;
    남아 있는 canvas와 상태만 제거하고 즉시 종료한다. 새 공개 이름에는&lt;br /&gt;
    프로젝트 접두사를 사용하지 않는 규칙을 유지한다.&lt;br /&gt;
    */&lt;br /&gt;
    function removeRetiredBitmap() {&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-bitmap&#039;),&lt;br /&gt;
            function (canvas) {&lt;br /&gt;
                if (canvas.parentNode) canvas.parentNode.removeChild(canvas);&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-inner&#039;),&lt;br /&gt;
            function (inner) {&lt;br /&gt;
                inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;data-bitmap-width&#039;,&lt;br /&gt;
                    &#039;data-bitmap-optical-shift-x&#039;,&lt;br /&gt;
                    &#039;data-bitmap-state&#039;,&lt;br /&gt;
                    &#039;data-bitmap-error&#039;&lt;br /&gt;
                ].forEach(function (name) {&lt;br /&gt;
                    inner.removeAttribute(name);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    removeRetiredBitmap();&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(removeRetiredBitmap);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreRetiredBitmapHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:&#039;retired-20260713-divider-no-track-001&#039;,&lt;br /&gt;
        recalculate:removeRetiredBitmap,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            return {&lt;br /&gt;
                integrated:false,&lt;br /&gt;
                state:&#039;retired&#039;,&lt;br /&gt;
                canvas:!!document.querySelector(&#039;.main-portal .main-manifesto-bitmap&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    var VERSION = &#039;20260712-bitmap18-inline-002&#039;;&lt;br /&gt;
    var SELECTOR = &#039;.main-portal .main-manifesto-inner&#039;;&lt;br /&gt;
    var CELL_W = 24;&lt;br /&gt;
    var CELL_H = 24;&lt;br /&gt;
    var GLYPHS = {&amp;quot;진&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,25056,25072,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;창&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,13280,78328,258272,12768,14128,13848,4360,3840,7040,12672,4480,8064,3840,0,0],&amp;quot;바&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12672,12672,14080,13056,13064,258456,127472,12784,12504,12408,12312,12288,12288,4096,0,0],&amp;quot;다&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,12792,12400,12320,258096,128528,14328,13048,12304,12288,12288,12288,4096,0,0],&amp;quot;는&amp;quot;:[19,0,0,0,0,0,0,96,192,192,14528,16064,960,0,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;왕&amp;quot;:[19,0,0,0,0,0,12288,28768,25568,25456,25392,25392,254944,24960,16064,10236,12344,7680,14080,12672,12672,16128,7936,0,0],&amp;quot;과&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,26560,26352,26112,26112,26208,255680,254912,29248,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;노&amp;quot;:[19,0,0,0,0,0,0,0,0,96,192,192,4288,15552,16320,7040,7168,3072,50688,131064,124,0,0,0,0],&amp;quot;예&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,24048,24504,23320,23320,24504,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;를&amp;quot;:[19,0,0,0,0,0,7168,16320,6144,8128,448,15552,1984,122880,262140,16636,8128,8128,3840,8064,1792,960,65408,4096,0],&amp;quot;같&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,110976,127168,12384,12344,4104,3584,0,8064,1920,256,14720,8064,0,0],&amp;quot;은&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,130944,115708,16,448,448,128,128,16256,7936,0,0],&amp;quot;깊&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,25024,24800,24624,8216,12288,16256,4864,15872,7680,31232,32640,256,0],&amp;quot;이&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,25072,25584,25368,25368,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;로&amp;quot;:[19,0,0,0,0,0,0,0,15360,16320,6272,7168,8128,384,14528,16320,7040,3072,50688,131064,124,0,0,0,0],&amp;quot;삼&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,78064,258272,13296,14136,13324,4096,16256,16256,4224,4480,8064,8064,0,0],&amp;quot;켰&amp;quot;:[19,0,0,0,0,0,12288,24576,26112,26600,29560,32656,25072,31992,26720,8224,8216,13056,15104,5056,15232,28544,58992,0,0],&amp;quot;고&amp;quot;:[19,0,0,0,0,0,0,0,0,7680,16352,12416,12288,12288,12544,13056,6912,4864,49920,131064,124,0,0,0,0],&amp;quot;,&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,8,0,0,0],&amp;quot;겨&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,32560,31104,24768,31840,31792,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;울&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,114688,262140,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;사&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12352,12480,12480,12480,12400,258272,127920,14104,13828,13312,12288,12288,12288,4096,0,0],&amp;quot;람&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13296,13240,78208,258552,12304,14224,13296,4096,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;짐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,26592,26160,9756,0,32512,32640,12672,12672,16128,16128,0,0],&amp;quot;승&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,130816,115708,272,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;의&amp;quot;:[19,0,0,0,0,0,28672,57344,24608,25536,26592,26160,26160,26464,25568,24576,24576,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;살&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,110832,127456,13232,13848,5132,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;을&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,32768,130944,115708,0,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;빛&amp;quot;:[19,0,0,0,0,0,12288,24576,25344,25344,26376,25496,25592,25584,25552,25088,7168,12288,32640,6400,7680,32256,25472,0,0],&amp;quot;으&amp;quot;:[19,0,0,0,0,0,0,0,128,3968,8064,4544,4288,6336,7616,3968,0,0,57344,131068,56,0,0,0,0],&amp;quot;얼&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25008,32536,31512,25016,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;렸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,32568,27008,31224,31792,26544,9200,8192,13056,15104,4928,15232,28544,58976,0,0],&amp;quot;.&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0],&amp;quot;그&amp;quot;:[19,0,0,0,0,0,0,0,0,7168,16352,12672,12288,12288,12288,12288,6144,4096,57344,131068,56,0,0,0,0],&amp;quot;들&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130048,118780,120,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;돌&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,8128,130048,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;벽&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25344,32520,31512,25560,32632,25584,9104,8192,31744,32640,8192,8192,8192,8192,0,0],&amp;quot;세&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22720,22720,22752,24432,22752,22960,23320,23300,22528,18432,16384,16384,24576,0,0],&amp;quot;우&amp;quot;:[19,0,0,0,0,0,0,384,7936,6528,12480,6336,7552,3968,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;땅&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,28560,9200,74160,254128,30936,16376,12280,12544,7936,13184,12672,12672,8064,3840,0,0],&amp;quot;찢&amp;quot;:[19,0,0,0,0,0,12288,24576,24704,26616,28656,26560,25456,28656,28056,26848,8192,32512,16128,7680,15872,25344,16768,0,0],&amp;quot;어&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,25520,32536,27416,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;길&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,24768,24672,24624,8216,16256,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;냈&amp;quot;:[19,0,0,0,0,0,8192,58368,19456,27672,27704,31792,31792,32560,28656,27120,8704,13824,14080,13248,15232,28544,50272,0,0],&amp;quot;며&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,26600,32536,25368,25400,32560,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;쇠&amp;quot;:[19,0,0,0,0,0,28672,57344,24704,24960,24960,25056,25536,26208,27888,24960,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;에&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,23024,23480,24344,24344,22968,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;제&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23488,23032,22976,24512,24184,23024,23344,23320,23052,18432,16384,16384,24576,0,0],&amp;quot;름&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,384,130560,116732,56,15872,16320,4288,4544,7552,8064,0,0],&amp;quot;새&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,27840,27840,31968,31856,23776,23984,20248,20228,16384,16384,16384,16384,24576,0,0],&amp;quot;겼&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,30712,32640,25024,31968,26672,8216,8200,15104,15104,6976,15232,28608,58992,0,0],&amp;quot;감&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,4120,4096,16256,16320,4224,4480,8064,8064,0,0],&amp;quot;옥&amp;quot;:[19,0,0,0,0,0,1792,7936,6528,6272,6528,8064,3584,1536,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;되&amp;quot;:[19,0,0,0,0,0,28672,57344,25088,26608,25072,24672,25632,26416,26608,25456,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;군&amp;quot;:[19,0,0,0,0,0,0,7680,16352,12288,12288,12288,6144,126976,262140,18172,1536,1728,1472,192,192,16256,8064,0,0],&amp;quot;대&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28032,28144,31856,31776,23600,24344,20440,19960,16400,16384,16384,16384,24576,0,0],&amp;quot;나&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12296,12312,12336,12304,259600,128784,14256,12528,12320,12288,12288,12288,4096,0,0],&amp;quot;르&amp;quot;:[19,0,0,0,0,0,0,0,6144,16320,6336,7168,8128,448,12480,16320,3968,0,57344,131068,56,0,0,0,0],&amp;quot;오&amp;quot;:[19,0,0,0,0,0,0,0,256,7936,15232,12672,12416,12672,8064,7936,3072,1024,49664,131064,124,0,0,0,0],&amp;quot;래&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28144,27064,32128,32248,23664,23568,20240,20464,16880,16384,16384,16384,24576,0,0],&amp;quot;된&amp;quot;:[19,0,0,0,0,0,28672,57344,26368,25584,24800,25696,28656,26608,29440,32736,25080,24832,25344,768,768,65280,15872,0,0],&amp;quot;원&amp;quot;:[19,0,0,0,0,0,28672,57440,25536,26464,26160,26160,29664,32640,26592,26620,25360,25024,25536,768,768,65280,15872,0,0],&amp;quot;한&amp;quot;:[19,0,0,0,0,0,6144,28704,12512,12800,14328,12784,258528,13088,12592,12784,12512,12416,4544,384,128,16256,7936,0,0],&amp;quot;함&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,14328,78032,258528,13104,12720,12784,4192,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;께&amp;quot;:[19,0,0,0,0,0,24576,55296,55296,55296,55488,57336,57280,57184,57184,55600,55704,55500,55392,49152,49152,16384,16384,0,0],&amp;quot;음&amp;quot;:[19,0,0,0,0,0,1920,8064,6528,6336,6336,3968,1792,32768,131040,16892,0,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;손&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,26304,1584,130560,249852,120,448,448,128,128,16256,7936,0,0],&amp;quot;넘&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,31792,31792,24624,28208,26608,8416,0,32512,32640,12672,12672,16256,15616,0,0],&amp;quot;갔&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,12312,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;\n&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],&amp;quot;줄&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,3840,15296,12480,41056,130976,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;처&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,25072,32448,27616,25392,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;부&amp;quot;:[19,0,0,0,0,0,6144,12288,28768,14400,15552,13248,8128,7872,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;터&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25056,24768,25344,25584,31984,31840,25648,28568,26360,24624,24576,24576,8192,8192,0,0],&amp;quot;목&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;둘&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130944,116732,1592,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;레&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23024,22968,22912,24568,22640,22544,23312,23536,23024,18432,16384,16384,24576,0,0],&amp;quot;있&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,25400,25368,25368,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;었&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32568,32536,25496,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;끝&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,3840,3584,8128,1920,384,16320,4032,0,0],&amp;quot;속&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,7936,12672,26336,1568,130560,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;뿌&amp;quot;:[19,0,0,0,0,0,4096,30720,14080,15904,16352,13280,16320,14208,4608,130944,126972,1552,1536,1536,1536,1536,512,0,0],&amp;quot;리&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,24960,25040,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;와&amp;quot;:[19,0,0,0,0,0,12288,28672,24608,25568,25456,26160,26416,25584,254432,254336,28864,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;빨&amp;quot;:[19,0,0,0,0,0,12288,28672,26112,26176,26304,255944,255992,10200,10224,1728,16256,16256,7936,7936,1792,896,65024,0,0],&amp;quot;굶&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,118784,262140,18428,1536,14080,16368,13792,14272,14272,16352,16352,0,0],&amp;quot;주&amp;quot;:[19,0,0,0,0,0,0,14336,16320,3264,1792,7936,12736,24672,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;림&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,25400,25472,25072,24624,26544,9200,8192,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;매&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28544,28644,32156,32024,23832,23992,19952,19504,16384,16384,16384,16384,24576,0,0],&amp;quot;였&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,32752,32568,25368,32664,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;른&amp;quot;:[19,0,0,0,0,0,4096,16320,6336,7744,4032,14528,16320,896,126976,253948,120,64,448,128,128,16256,7936,0,0],&amp;quot;아&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12304,12528,12720,13080,258840,127896,12784,12352,12288,12288,12288,12288,4096,0,0],&amp;quot;직&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,25568,26160,9752,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;태&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27840,28032,32240,31856,23600,24336,20440,20472,16432,16384,16384,16384,24576,0,0],&amp;quot;지&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25584,24992,24768,24816,25568,26160,27672,25612,24576,24576,24576,8192,0,0],&amp;quot;않&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,7168,6240,32352,15456,13408,14176,8160,0,0],&amp;quot;자&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13304,12720,12480,258296,127984,14128,13848,13324,12288,12288,12288,4096,0,0],&amp;quot;졌&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,32752,31136,32240,32736,26160,9756,8704,13056,15104,4928,15232,28544,50784,0,0],&amp;quot;앞&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,6144,8128,2432,7936,2816,15616,16320,128,0],&amp;quot;갈&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,127360,127168,12384,12344,4104,16256,8064,3840,8064,1792,896,65280,0,0],&amp;quot;때&amp;quot;:[19,0,0,0,0,0,24576,52224,55296,55296,57096,56312,63920,63632,63704,56536,57336,57336,49216,49152,49152,16384,16384,0,0],&amp;quot;마&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13284,13084,13080,258840,127792,12784,12464,12288,12288,12288,12288,4096,0,0],&amp;quot;듭&amp;quot;:[19,0,0,0,0,0,6144,16320,1984,384,12480,16064,3008,32768,131008,115708,14336,12352,12480,14720,16256,8064,7808,0,0],&amp;quot;파&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13248,12792,12688,13296,258528,127392,16352,12412,12288,12288,12288,12288,4096,0,0],&amp;quot;각&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,12672,258240,12384,12336,4120,4096,16128,13248,12288,4096,4096,4096,0,0],&amp;quot;쥔&amp;quot;:[19,0,0,0,0,0,28672,57856,26608,25584,25024,26592,28208,27664,28544,28668,25360,24960,25536,768,768,65280,15872,0,0],&amp;quot;당&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,12536,77920,258096,15888,14328,12920,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;기&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,25392,24960,24768,24672,24624,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;면&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26604,32536,25368,32560,31728,25072,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;풀&amp;quot;:[19,0,0,0,0,0,12288,16352,7296,8064,3328,32704,41440,131008,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;려&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,32696,31104,25040,31856,27696,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;날&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12312,12344,110640,128560,14256,13296,4192,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;것&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,24960,32704,26848,24624,24600,11272,3072,1792,3584,15104,29056,24800,0,0],&amp;quot;라&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13280,12728,12672,12792,258168,127504,13840,14320,12784,12288,12288,12288,4096,0,0],&amp;quot;여&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,32688,25368,25368,32664,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;므&amp;quot;:[19,0,0,0,0,0,0,0,4096,31744,32736,6624,3264,1728,16064,7360,0,0,57344,131068,56,0,0,0,0],&amp;quot;번&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25344,26376,26392,32728,25464,25584,25552,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;도&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,4064,960,384,14528,16064,7136,7360,3072,50688,131064,124,0,0,0,0],&amp;quot;느&amp;quot;:[19,0,0,0,0,0,0,0,0,32,192,192,192,14528,16064,7104,0,0,57344,131068,56,0,0,0,0],&amp;quot;슨&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,24768,48,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;해&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27968,28664,32240,32224,23984,23984,19952,19568,16384,16384,16384,16384,24576,0,0],&amp;quot;았&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12528,12784,78616,258840,13208,12784,12512,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;침&amp;quot;:[19,0,0,0,0,0,12288,24768,25024,25088,26592,25584,24800,25568,26416,9232,8,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;내&amp;quot;:[19,0,0,0,0,0,24576,58368,19456,19456,27656,27672,31792,31792,31760,24368,20464,19952,16384,16384,24576,24576,8192,0,0],&amp;quot;멎&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25576,32536,31512,25392,25072,8224,8192,32640,6912,3584,15872,25344,16768,0,0],&amp;quot;서&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,32496,24800,25520,26392,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;긴&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26368,25592,25520,25024,24768,24688,24632,24584,25472,8960,768,768,32512,15872,0,0],&amp;quot;붙&amp;quot;:[19,0,0,0,0,0,6144,12288,14432,16064,13248,7872,38912,130816,116732,1552,3840,3584,8064,1920,384,15808,4032,0,0],&amp;quot;잡&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,111040,258288,13280,13872,5660,4096,12352,12416,14720,16256,8064,7296,0,0],&amp;quot;피&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25592,25392,25568,25440,24992,28640,26744,24576,24576,24576,24576,8192,0,0],&amp;quot;가&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,13304,12728,12736,127168,258144,12336,12312,12300,12288,12288,12288,4096,0,0],&amp;quot;굴&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,126976,262140,18172,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;보&amp;quot;:[19,0,0,0,0,0,0,0,6144,12288,28768,14528,16064,13248,16320,7360,3072,1024,50688,131064,124,0,0,0,0],&amp;quot;하&amp;quot;:[19,0,0,0,0,0,6144,12288,12320,12512,12800,14332,12792,12512,259040,127792,12720,12528,12288,12288,12288,12288,4096,0,0],&amp;quot;입&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25584,25368,25368,25496,25072,8256,4096,28800,24960,14720,16128,16128,15616,0,0],&amp;quot;말&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13308,111384,127768,13240,12784,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;했&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,28664,27888,32224,23984,19888,18672,96,13824,15872,13184,15232,28544,50272,0,0],&amp;quot;조&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,8128,3072,1792,16128,28864,25184,1568,1536,50688,131064,124,0,0,0,0],&amp;quot;올&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6528,8064,3840,1536,128768,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;수&amp;quot;:[19,0,0,0,0,0,0,1536,3072,1792,1792,6912,29056,24800,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;록&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,3968,128512,253948,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;열&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,32688,31512,25368,32696,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;단&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,12792,12400,258096,13872,14232,14328,12336,12672,4480,384,384,16256,7936,0,0],&amp;quot;질&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25584,24992,25072,26464,26160,9240,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;히&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25024,26112,26616,25584,25024,25440,25392,25392,25056,24576,24576,24576,8192,8192,0,0],&amp;quot;운&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;데&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22912,23024,22640,24352,22576,23320,23512,23032,22544,18432,16384,16384,24576,0,0],&amp;quot;끌&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;린&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25568,25464,24960,25592,24624,26416,26608,24672,8320,9088,256,256,32512,15872,0,0],&amp;quot;게&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,22960,24000,24512,22624,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;누&amp;quot;:[19,0,0,0,0,0,0,96,192,192,192,15552,16320,960,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;인&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24624,25072,25400,25368,25368,25072,24800,24576,9088,8960,256,256,32512,15872,0,0],&amp;quot;쳐&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,32752,31936,25568,32560,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;문&amp;quot;:[19,0,0,0,0,0,12288,15872,15328,6336,3264,7872,7360,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;몸&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;식&amp;quot;:[19,0,0,0,0,0,12288,24576,24768,24960,25024,24816,24800,25568,26160,9244,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;억&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32536,32536,25368,25072,8416,0,31744,32640,12288,12288,12288,12288,0,0],&amp;quot;전&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25592,24992,31984,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;품&amp;quot;:[19,0,0,0,0,0,12288,16352,7360,8064,7040,16128,26592,127040,253948,18040,1536,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;발&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,111512,127992,13176,13272,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;머&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,25576,25368,32536,27448,25392,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;두&amp;quot;:[19,0,0,0,0,0,0,14336,8128,960,384,12480,16064,5056,192,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;닐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,24624,24624,27696,28464,26608,8288,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;격&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26616,32640,25024,31968,26672,8216,8200,32256,26496,8192,8192,8192,8192,0,0],&amp;quot;없&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25072,32536,31512,25496,25072,8256,8192,13824,15936,4928,15296,28608,51008,0,0],&amp;quot;니&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24584,24624,24624,24624,25648,28208,26544,25328,24576,24576,24576,8192,8192,0,0],&amp;quot;산&amp;quot;:[19,0,0,0,0,0,6144,28672,12352,12480,12480,12496,258272,13280,14128,13852,12288,12672,4480,384,384,16256,7936,0,0],&amp;quot;복&amp;quot;:[19,0,0,0,0,0,14336,12288,28736,15552,14272,16320,7808,3072,130560,249852,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;종&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,26208,1568,130816,116732,280,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;죽&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,24672,122912,262140,18172,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;망&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,78620,258840,13112,12784,12336,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;베&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22912,22912,24448,24448,24456,24536,23024,23024,22776,22648,18456,16384,16384,24576,0,0],&amp;quot;막&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,13084,258840,13080,12784,12464,4096,15872,16320,12288,4096,4096,4096,0,0],&amp;quot;무&amp;quot;:[19,0,0,0,0,0,0,30720,32608,15296,6336,3520,16320,15488,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;릎&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,8128,0,130560,116732,56,16320,3456,8064,3840,16128,14272,0,0],&amp;quot;꿇&amp;quot;:[19,0,0,0,0,0,0,14720,16368,12640,12672,12672,118912,262140,18428,1536,7936,7152,32736,14784,11712,16352,7648,0,0],&amp;quot;까&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,24576,24768,10232,10176,9824,254752,254768,8600,8388,8288,8192,8192,8192,12288,0,0],&amp;quot;놓&amp;quot;:[19,0,0,0,0,0,0,224,192,12480,15552,8128,7552,130048,253948,16760,1792,15872,16320,3584,6912,3328,1792,0,0],&amp;quot;러&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,29056,32720,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;재&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28608,28152,32192,31936,23672,24048,20272,20248,16908,16384,16384,16384,24576,0,0],&amp;quot;받&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,78744,259064,13176,13304,12672,0,7936,3584,768,24960,32640,1920,0,0],&amp;quot;묵&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,3200,130048,118780,1592,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;스&amp;quot;:[19,0,0,0,0,0,0,0,512,1536,3584,1920,1792,7936,12672,24800,0,0,57344,131068,56,0,0,0,0],&amp;quot;릴&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25592,25360,25536,25072,25136,26608,8432,14464,16256,7936,16128,3840,1792,130560,0,0],&amp;quot;계&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,24496,24256,22720,24416,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;구&amp;quot;:[19,0,0,0,0,0,0,6144,16352,12736,12288,12288,12288,6144,4096,131056,67324,1536,1536,1536,1536,1536,512,0,0],&amp;quot;물&amp;quot;:[19,0,0,0,0,0,14336,32544,15328,3264,7872,16064,32768,131040,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;못&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,7360,3776,16064,7296,3072,130816,116732,1048,3584,1536,1792,7936,12480,8304,0,0],&amp;quot;시&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,24816,24800,25520,26424,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;육&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6336,6528,3968,1536,130048,118780,6584,6528,16256,15296,4096,4096,4096,4096,0,0],&amp;quot;신&amp;quot;:[19,0,0,0,0,0,12288,24576,24640,25024,25024,24768,24800,25568,26416,25628,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;일&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25584,25368,25368,25528,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;모&amp;quot;:[19,0,0,0,0,0,0,0,14336,32256,16352,6368,3264,7872,16064,6144,2048,1024,50688,131064,124,0,0,0,0],&amp;quot;행&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,20472,27888,32224,32176,23984,19696,25184,15872,30208,25344,25344,16128,7680,0,0],&amp;quot;절&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,32160,32752,26464,26160,9752,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;멸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,32760,31512,25400,32688,25072,8224,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;끊&amp;quot;:[19,0,0,0,0,0,0,14592,16368,13280,12672,12672,12672,114688,262140,16636,7168,6240,32352,15456,13408,16224,7648,0,0],&amp;quot;순&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,131008,116732,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;간&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,13304,12688,258240,12384,12336,12312,12296,12672,4480,384,384,16256,7936,0,0]};&lt;br /&gt;
    var BODY_SCALE = 1;&lt;br /&gt;
    var TITLE_SCALE = 2;&lt;br /&gt;
    var BODY_TRACKING = -2;&lt;br /&gt;
    var TITLE_TRACKING = 0;&lt;br /&gt;
    var SPACE_ADVANCE = Math.round(CELL_H * 0.5);&lt;br /&gt;
    var BODY_LINE_HEIGHT = CELL_H + 8;&lt;br /&gt;
    var PARAGRAPH_GAP = 20;&lt;br /&gt;
    var DIVIDER_Y = 52;&lt;br /&gt;
    var BODY_TOP = 68;&lt;br /&gt;
    var BODY_COLOR = &#039;#d8d8d8&#039;;&lt;br /&gt;
    var TITLE_COLOR = &#039;#ffffff&#039;;&lt;br /&gt;
    var DIVIDER_COLOR = &#039;#555555&#039;;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var observed = typeof WeakSet === &#039;function&#039; ? new WeakSet() : null;&lt;br /&gt;
    var resizeObserver = typeof ResizeObserver === &#039;function&#039; ? new ResizeObserver(function (entries) {&lt;br /&gt;
        entries.forEach(function (entry) { schedule(entry.target, false); });&lt;br /&gt;
    }) : null;&lt;br /&gt;
&lt;br /&gt;
    function glyphFor(character) {&lt;br /&gt;
        return Object.prototype.hasOwnProperty.call(GLYPHS, character) ? GLYPHS[character] : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function measureText(text, scale, tracking) {&lt;br /&gt;
        var width = 0;&lt;br /&gt;
        var glyphCount = 0;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                width += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            width += glyph[0] * scale;&lt;br /&gt;
            width += tracking * scale;&lt;br /&gt;
            glyphCount += 1;&lt;br /&gt;
        });&lt;br /&gt;
        if (glyphCount &amp;amp;&amp;amp; tracking) width -= tracking * scale;&lt;br /&gt;
        return Math.max(0, Math.round(width));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function breakLongToken(token, maxWidth) {&lt;br /&gt;
        var pieces = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
        Array.from(token).forEach(function (character) {&lt;br /&gt;
            var candidate = current + character;&lt;br /&gt;
            if (current &amp;amp;&amp;amp; measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;gt; maxWidth) {&lt;br /&gt;
                pieces.push(current);&lt;br /&gt;
                current = character;&lt;br /&gt;
            } else {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        if (current) pieces.push(current);&lt;br /&gt;
        return pieces;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wrapParagraph(text, maxWidth) {&lt;br /&gt;
        var words = String(text || &#039;&#039;).trim().split(/\s+/).filter(Boolean);&lt;br /&gt;
        var lines = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        words.forEach(function (word) {&lt;br /&gt;
            var candidate = current ? current + &#039; &#039; + word : word;&lt;br /&gt;
            var pieces;&lt;br /&gt;
            if (measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (current) {&lt;br /&gt;
                lines.push(current);&lt;br /&gt;
                current = &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (measureText(word, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = word;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            pieces = breakLongToken(word, maxWidth);&lt;br /&gt;
            pieces.forEach(function (piece, index) {&lt;br /&gt;
                if (index === pieces.length - 1) current = piece;&lt;br /&gt;
                else lines.push(piece);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        if (current) lines.push(current);&lt;br /&gt;
        return lines.length ? lines : [&#039;&#039;];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateOpticalShift(lines, width) {&lt;br /&gt;
        var weightedCenter = 0;&lt;br /&gt;
        var totalWeight = 0;&lt;br /&gt;
        var maxLineWidth = 0;&lt;br /&gt;
        var shift;&lt;br /&gt;
        lines.forEach(function (line) {&lt;br /&gt;
            var lineWidth = line.width;&lt;br /&gt;
            var weight = Math.max(1, lineWidth) * BODY_LINE_HEIGHT;&lt;br /&gt;
            weightedCenter += (lineWidth / 2) * weight;&lt;br /&gt;
            totalWeight += weight;&lt;br /&gt;
            maxLineWidth = Math.max(maxLineWidth, lineWidth);&lt;br /&gt;
        });&lt;br /&gt;
        if (!totalWeight) return 0;&lt;br /&gt;
        shift = Math.round((width / 2) - (weightedCenter / totalWeight));&lt;br /&gt;
        return Math.max(0, Math.min(Math.max(0, width - maxLineWidth), shift));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawGlyph(context, glyph, x, y, scale) {&lt;br /&gt;
        var py;&lt;br /&gt;
        var px;&lt;br /&gt;
        var row;&lt;br /&gt;
        for (py = 0; py &amp;lt; CELL_H; py += 1) {&lt;br /&gt;
            row = glyph[py + 1] || 0;&lt;br /&gt;
            if (!row) continue;&lt;br /&gt;
            for (px = 0; px &amp;lt; CELL_W; px += 1) {&lt;br /&gt;
                if (row &amp;amp; (1 &amp;lt;&amp;lt; px)) {&lt;br /&gt;
                    context.fillRect(&lt;br /&gt;
                        Math.round(x + px * scale),&lt;br /&gt;
                        Math.round(y + py * scale),&lt;br /&gt;
                        scale,&lt;br /&gt;
                        scale&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawText(context, text, x, y, scale, tracking, color) {&lt;br /&gt;
        context.fillStyle = color;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                x += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            drawGlyph(context, glyph, x, y, scale);&lt;br /&gt;
            x += (glyph[0] + tracking) * scale;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sourceContent(inner) {&lt;br /&gt;
        var titleNode = inner.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        var paragraphs = [];&lt;br /&gt;
        Array.prototype.forEach.call(inner.children || [], function (child) {&lt;br /&gt;
            if (child &amp;amp;&amp;amp; child.tagName === &#039;P&#039;) {&lt;br /&gt;
                var value = String(child.textContent || &#039;&#039;).trim();&lt;br /&gt;
                if (value) paragraphs.push(value);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        return {&lt;br /&gt;
            title: titleNode ? String(titleNode.textContent || &#039;&#039;).trim() : &#039;&#039;,&lt;br /&gt;
            paragraphs: paragraphs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureCanvas(inner) {&lt;br /&gt;
        var canvas = inner.querySelector(&#039;.main-manifesto-bitmap&#039;);&lt;br /&gt;
        if (!canvas) {&lt;br /&gt;
            canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            canvas.className = &#039;main-manifesto-bitmap&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            canvas.setAttribute(&#039;data-bitmap-version&#039;, VERSION);&lt;br /&gt;
            inner.insertBefore(canvas, inner.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
        return canvas;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(inner, force) {&lt;br /&gt;
        var width;&lt;br /&gt;
        var source;&lt;br /&gt;
        var paragraphs;&lt;br /&gt;
        var flatLines = [];&lt;br /&gt;
        var titleWidth;&lt;br /&gt;
        var height;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var context;&lt;br /&gt;
        var y;&lt;br /&gt;
        var opticalShift;&lt;br /&gt;
&lt;br /&gt;
        if (!inner || !inner.isConnected) return;&lt;br /&gt;
        width = Math.max(1, Math.floor(inner.clientWidth || inner.getBoundingClientRect().width || 0));&lt;br /&gt;
        if (width &amp;lt;= 1) {&lt;br /&gt;
            window.setTimeout(function () { schedule(inner, true); }, 60);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (!force &amp;amp;&amp;amp; Number(inner.getAttribute(&#039;data-bitmap-width&#039;)) === width &amp;amp;&amp;amp; inner.classList.contains(&#039;is-bitmap-ready&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        source = sourceContent(inner);&lt;br /&gt;
        if (!source.title || !source.paragraphs.length) {&lt;br /&gt;
            fail(inner, new Error(&#039;Manifesto source nodes were not found.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        paragraphs = source.paragraphs.map(function (paragraph) {&lt;br /&gt;
            return wrapParagraph(paragraph, width).map(function (line) {&lt;br /&gt;
                var item = { text:line, width:measureText(line, BODY_SCALE, BODY_TRACKING) };&lt;br /&gt;
                flatLines.push(item);&lt;br /&gt;
                return item;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        titleWidth = measureText(source.title, TITLE_SCALE, TITLE_TRACKING);&lt;br /&gt;
        height = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, index) {&lt;br /&gt;
            height += lines.length * BODY_LINE_HEIGHT;&lt;br /&gt;
            if (index &amp;lt; paragraphs.length - 1) height += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
        height = Math.max(height, DIVIDER_Y + 2);&lt;br /&gt;
&lt;br /&gt;
        canvas = ensureCanvas(inner);&lt;br /&gt;
        canvas.width = width;&lt;br /&gt;
        canvas.height = Math.ceil(height);&lt;br /&gt;
        canvas.style.width = width + &#039;px&#039;;&lt;br /&gt;
        canvas.style.height = Math.ceil(height) + &#039;px&#039;;&lt;br /&gt;
        context = canvas.getContext(&#039;2d&#039;, { alpha:true });&lt;br /&gt;
        if (!context) {&lt;br /&gt;
            fail(inner, new Error(&#039;Canvas 2D context is unavailable.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        context.imageSmoothingEnabled = false;&lt;br /&gt;
        context.clearRect(0, 0, canvas.width, canvas.height);&lt;br /&gt;
&lt;br /&gt;
        drawText(&lt;br /&gt;
            context,&lt;br /&gt;
            source.title,&lt;br /&gt;
            Math.round((width - titleWidth) / 2),&lt;br /&gt;
            0,&lt;br /&gt;
            TITLE_SCALE,&lt;br /&gt;
            TITLE_TRACKING,&lt;br /&gt;
            TITLE_COLOR&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /* 완성형 구분선은 폐기했다. 실선 애니메이션은 ManifestoIntro만 담당한다. */&lt;br /&gt;
&lt;br /&gt;
        opticalShift = calculateOpticalShift(flatLines, width);&lt;br /&gt;
        y = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, paragraphIndex) {&lt;br /&gt;
            lines.forEach(function (line) {&lt;br /&gt;
                drawText(context, line.text, opticalShift, y, BODY_SCALE, BODY_TRACKING, BODY_COLOR);&lt;br /&gt;
                y += BODY_LINE_HEIGHT;&lt;br /&gt;
            });&lt;br /&gt;
            if (paragraphIndex &amp;lt; paragraphs.length - 1) y += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-width&#039;, String(width));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-optical-shift-x&#039;, String(opticalShift));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        inner.classList.add(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fail(inner, error) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;failed&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-error&#039;, String(error &amp;amp;&amp;amp; error.message ? error.message : error));&lt;br /&gt;
        try { console.error(&#039;[MainPageBitmap]&#039;, error); } catch (ignore) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(inner, force) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            try { render(inner, force); }&lt;br /&gt;
            catch (error) { fail(inner, error); }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mount(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var nodes = [];&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(SELECTOR)) nodes.push(scope);&lt;br /&gt;
        nodes = nodes.concat(Array.prototype.slice.call(scope.querySelectorAll(SELECTOR)));&lt;br /&gt;
        nodes.forEach(function (inner) {&lt;br /&gt;
            if (resizeObserver &amp;amp;&amp;amp; (!observed || !observed.has(inner))) {&lt;br /&gt;
                resizeObserver.observe(inner);&lt;br /&gt;
                if (observed) observed.add(inner);&lt;br /&gt;
            }&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function recalculate() {&lt;br /&gt;
        Array.prototype.forEach.call(document.querySelectorAll(SELECTOR), function (inner) {&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot() {&lt;br /&gt;
        mount(document);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 120);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) document.addEventListener(&#039;DOMContentLoaded&#039;, boot, { once:true });&lt;br /&gt;
    else boot();&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        window.clearTimeout(resizeTimer);&lt;br /&gt;
        resizeTimer = window.setTimeout(recalculate, 100);&lt;br /&gt;
    }, { passive:true });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                mount(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        recalculate:recalculate,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            var inner = document.querySelector(SELECTOR);&lt;br /&gt;
            return inner ? {&lt;br /&gt;
                integrated:true,&lt;br /&gt;
                state:inner.getAttribute(&#039;data-bitmap-state&#039;) || &#039;pending&#039;,&lt;br /&gt;
                error:inner.getAttribute(&#039;data-bitmap-error&#039;) || &#039;&#039;,&lt;br /&gt;
                width:inner.getAttribute(&#039;data-bitmap-width&#039;) || &#039;&#039;,&lt;br /&gt;
                opticalShiftX:inner.getAttribute(&#039;data-bitmap-optical-shift-x&#039;) || &#039;&#039;,&lt;br /&gt;
                canvas:!!inner.querySelector(&#039;.main-manifesto-bitmap&#039;)&lt;br /&gt;
            } : { integrated:true, state:&#039;not-mounted&#039; };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    window.MainPageManifesto = window.MainPageManifesto || {};&lt;br /&gt;
    window.MainPageManifesto.recalculate = recalculate;&lt;br /&gt;
})(window, document, window.mw);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4676</id>
		<title>미디어위키:CategoryNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4676"/>
		<updated>2026-07-17T04:00:18Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-pixel-stack-20260717 / js/CategoryNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - CategoryNav&lt;br /&gt;
대문 카테고리 SVG 네비 생성기&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var ITEMS = [&lt;br /&gt;
        { key:&#039;era&#039;, label:&#039;시대&#039;, title:&#039;시대&#039; },&lt;br /&gt;
        { key:&#039;settings&#039;, label:&#039;설정&#039;, title:&#039;설정&#039; },&lt;br /&gt;
        { key:&#039;project&#039;, label:&#039;프로젝트&#039;, title:&#039;프로젝트:소개&#039; },&lt;br /&gt;
        { key:&#039;corporations&#039;, label:&#039;기업 및 공동체&#039;, title:&#039;기업_및_공동체&#039; },&lt;br /&gt;
        { key:&#039;military&#039;, label:&#039;군, 정치집단&#039;, title:&#039;군_정치집단&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
    var BOUNDARY_DIRS = [0, 1, 1, -1, -1, 0];&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
&lt;br /&gt;
    function geometryApi() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) return window.buildWikiPath(title);&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearNode(node) {&lt;br /&gt;
        while (node &amp;amp;&amp;amp; node.firstChild) node.removeChild(node.firstChild);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateGeometry(width) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var frame = api.FRAME;&lt;br /&gt;
        var dimensions = api.DIMENSIONS;&lt;br /&gt;
        var contentLeft = dimensions.WELL_TOP;&lt;br /&gt;
        var contentWidth = Math.max(ITEMS.length, width - dimensions.WELL_TOP * 2);&lt;br /&gt;
        var boundaries = [];&lt;br /&gt;
        var segments = [];&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt;= ITEMS.length; i++) {&lt;br /&gt;
            boundaries.push(contentLeft + Math.round(contentWidth * i / ITEMS.length));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; ITEMS.length; i++) {&lt;br /&gt;
            var leftTopX = boundaries[i] + (i === 0 ? 0 : frame.GAP / 2);&lt;br /&gt;
            var rightTopX = boundaries[i + 1] - (i === ITEMS.length - 1 ? 0 : frame.GAP / 2);&lt;br /&gt;
            segments.push({&lt;br /&gt;
                leftTopX:leftTopX,&lt;br /&gt;
                rightTopX:rightTopX,&lt;br /&gt;
                leftDir:BOUNDARY_DIRS[i],&lt;br /&gt;
                rightDir:BOUNDARY_DIRS[i + 1],&lt;br /&gt;
                points:api.segmentAt(&lt;br /&gt;
                    leftTopX,&lt;br /&gt;
                    rightTopX,&lt;br /&gt;
                    dimensions.BUTTON_TOP,&lt;br /&gt;
                    dimensions.BUTTON_BOTTOM,&lt;br /&gt;
                    BOUNDARY_DIRS[i],&lt;br /&gt;
                    BOUNDARY_DIRS[i + 1],&lt;br /&gt;
                    0,&lt;br /&gt;
                    0,&lt;br /&gt;
                    0&lt;br /&gt;
                )&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            width:width,&lt;br /&gt;
            boundaries:boundaries,&lt;br /&gt;
            segments:segments,&lt;br /&gt;
            dimensions:dimensions&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendFrame(svg, geometry) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var frame = api.FRAME;&lt;br /&gt;
        var d = geometry.dimensions;&lt;br /&gt;
        var width = geometry.width;&lt;br /&gt;
        var group = api.create(&#039;g&#039;, &#039;portal-category-frame&#039;);&lt;br /&gt;
        var surfaceLastX = width - d.FRAME_SURFACE_TOP - 1;&lt;br /&gt;
        var surfaceLastY = d.ROW_HEIGHT - d.FRAME_SURFACE_TOP - 1;&lt;br /&gt;
        var wellLastX = width - d.WELL_TOP - 1;&lt;br /&gt;
        var wellLastY = d.WELL2_BOTTOM - 1;&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(api.rect(&#039;portal-category-frame-edge&#039;, 0, 0, width, d.ROW_HEIGHT));&lt;br /&gt;
        group.appendChild(api.rect(&lt;br /&gt;
            &#039;portal-category-frame-surface&#039;,&lt;br /&gt;
            d.FRAME_SURFACE_TOP,&lt;br /&gt;
            d.FRAME_SURFACE_TOP,&lt;br /&gt;
            width - d.FRAME_SURFACE_TOP * 2,&lt;br /&gt;
            d.ROW_HEIGHT - d.FRAME_SURFACE_TOP * 2&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.rect(&lt;br /&gt;
            &#039;portal-category-frame-well&#039;,&lt;br /&gt;
            d.WELL_TOP,&lt;br /&gt;
            d.WELL_TOP,&lt;br /&gt;
            width - d.WELL_TOP * 2,&lt;br /&gt;
            d.WELL2_BOTTOM - d.WELL_TOP&lt;br /&gt;
        ));&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-frame-highlight&#039;,&lt;br /&gt;
            [d.FRAME_SURFACE_TOP, d.FRAME_SURFACE_TOP],&lt;br /&gt;
            [surfaceLastX, d.FRAME_SURFACE_TOP]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-frame-highlight&#039;,&lt;br /&gt;
            [surfaceLastX, d.FRAME_SURFACE_TOP],&lt;br /&gt;
            [surfaceLastX, surfaceLastY]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-frame-shadow&#039;,&lt;br /&gt;
            [d.FRAME_SURFACE_TOP, surfaceLastY],&lt;br /&gt;
            [d.FRAME_SURFACE_TOP, d.FRAME_SURFACE_TOP]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-frame-shadow&#039;,&lt;br /&gt;
            [surfaceLastX, surfaceLastY],&lt;br /&gt;
            [d.FRAME_SURFACE_TOP, surfaceLastY]&lt;br /&gt;
        ));&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-shadow&#039;,&lt;br /&gt;
            [d.WELL_TOP, d.WELL_TOP],&lt;br /&gt;
            [wellLastX, d.WELL_TOP]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-shadow&#039;,&lt;br /&gt;
            [wellLastX, d.WELL_TOP],&lt;br /&gt;
            [wellLastX, wellLastY]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-highlight&#039;,&lt;br /&gt;
            [d.WELL_TOP, wellLastY],&lt;br /&gt;
            [d.WELL_TOP, d.WELL_TOP]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-highlight&#039;,&lt;br /&gt;
            [wellLastX, wellLastY],&lt;br /&gt;
            [d.WELL_TOP, wellLastY]&lt;br /&gt;
        ));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createSegment(item, index, geometry) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var frame = api.FRAME;&lt;br /&gt;
        var data = geometry.segments[index];&lt;br /&gt;
        var d = geometry.dimensions;&lt;br /&gt;
        var anchor = api.create(&#039;a&#039;, &#039;portal-cat-anchor&#039;);&lt;br /&gt;
        var outer = api.polygon(&#039;portal-cat-edge&#039;, data.points);&lt;br /&gt;
        var surfacePoints = api.segmentAt(&lt;br /&gt;
            data.leftTopX,&lt;br /&gt;
            data.rightTopX,&lt;br /&gt;
            d.BUTTON_SURFACE_TOP,&lt;br /&gt;
            d.BUTTON_SURFACE_BOTTOM,&lt;br /&gt;
            data.leftDir,&lt;br /&gt;
            data.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER,&lt;br /&gt;
            frame.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var topInset = api.rowAt(&lt;br /&gt;
            data.leftTopX,&lt;br /&gt;
            data.rightTopX,&lt;br /&gt;
            d.BUTTON_SURFACE_TOP,&lt;br /&gt;
            data.leftDir,&lt;br /&gt;
            data.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER,&lt;br /&gt;
            frame.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var bottomInset = api.rowAt(&lt;br /&gt;
            data.leftTopX,&lt;br /&gt;
            data.rightTopX,&lt;br /&gt;
            d.BUTTON_SURFACE_BOTTOM - frame.BEVEL,&lt;br /&gt;
            data.leftDir,&lt;br /&gt;
            data.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER,&lt;br /&gt;
            frame.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var hit = api.polygon(&#039;portal-cat-hit&#039;, data.points);&lt;br /&gt;
        var text = api.create(&#039;text&#039;, &#039;portal-cat-label&#039;);&lt;br /&gt;
        var href = buildPath(item.title);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, item.key);&lt;br /&gt;
&lt;br /&gt;
        anchor.appendChild(outer);&lt;br /&gt;
        anchor.appendChild(api.polygon(&#039;portal-cat-surface&#039;, surfacePoints));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-top&#039;, topInset[0], topInset[1]));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-right&#039;, topInset[1], bottomInset[1]));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-bottom&#039;, bottomInset[1], bottomInset[0]));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-left&#039;, bottomInset[0], topInset[0]));&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
        anchor.appendChild(hit);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, api.centerX(surfacePoints));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, Math.round((d.BUTTON_SURFACE_TOP + d.BUTTON_SURFACE_BOTTOM) / 2) + 1);&lt;br /&gt;
        text.setAttribute(&#039;font-size&#039;, &#039;12&#039;);&lt;br /&gt;
        text.setAttribute(&#039;font-weight&#039;, &#039;700&#039;);&lt;br /&gt;
        text.setAttribute(&#039;text-anchor&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;dominant-baseline&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        text.textContent = item.label;&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        return anchor;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function publishSeams(geometry) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var segments = geometry.segments;&lt;br /&gt;
        var d = geometry.dimensions;&lt;br /&gt;
&lt;br /&gt;
        api.setSeams({&lt;br /&gt;
            width:geometry.width,&lt;br /&gt;
            rowHeight:d.ROW_HEIGHT,&lt;br /&gt;
            buttonTop:d.BUTTON_TOP,&lt;br /&gt;
            buttonBottom:d.BUTTON_BOTTOM,&lt;br /&gt;
            overlap:d.OVERLAP,&lt;br /&gt;
            bottomStartAbsY:d.BOTTOM_START_ABS_Y,&lt;br /&gt;
            settingsLeftTopX:segments[1].leftTopX,&lt;br /&gt;
            settingsLeftDir:segments[1].leftDir,&lt;br /&gt;
            corpRightTopX:segments[3].rightTopX,&lt;br /&gt;
            corpRightDir:segments[3].rightDir,&lt;br /&gt;
            projectLeftTopX:segments[2].leftTopX,&lt;br /&gt;
            projectLeftDir:segments[2].leftDir,&lt;br /&gt;
            projectRightTopX:segments[2].rightTopX,&lt;br /&gt;
            projectRightDir:segments[2].rightDir&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildSvg(width) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var geometry = calculateGeometry(width);&lt;br /&gt;
        var svg = api.create(&#039;svg&#039;, &#039;portal-category-svg&#039;);&lt;br /&gt;
&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + geometry.width + &#039; &#039; + geometry.dimensions.ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, geometry.dimensions.ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;none&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;aria-hidden&#039;, &#039;false&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;data-frame-width&#039;, geometry.width);&lt;br /&gt;
&lt;br /&gt;
        appendFrame(svg, geometry);&lt;br /&gt;
        ITEMS.forEach(function (item, index) {&lt;br /&gt;
            svg.appendChild(createSegment(item, index, geometry));&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return { svg:svg, geometry:geometry };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderMount(mount) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var width;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var result;&lt;br /&gt;
&lt;br /&gt;
        if (!mount || !api) return false;&lt;br /&gt;
        width = api.readWidth(mount);&lt;br /&gt;
        if (!width) {&lt;br /&gt;
            if (!mount.__categoryNavRetry) {&lt;br /&gt;
                mount.__categoryNavRetry = window.setTimeout(function () {&lt;br /&gt;
                    mount.__categoryNavRetry = 0;&lt;br /&gt;
                    renderMount(mount);&lt;br /&gt;
                }, 60);&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (mount.__categoryNavRetry) {&lt;br /&gt;
            window.clearTimeout(mount.__categoryNavRetry);&lt;br /&gt;
            mount.__categoryNavRetry = 0;&lt;br /&gt;
        }&lt;br /&gt;
        nav = mount.querySelector(&#039;.portal-category-nav&#039;);&lt;br /&gt;
        if (!nav) {&lt;br /&gt;
            clearNode(mount);&lt;br /&gt;
            nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
            nav.className = &#039;portal-category-nav&#039;;&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, &#039;Main categories&#039;);&lt;br /&gt;
            mount.appendChild(nav);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (nav.__frameWidth === width &amp;amp;&amp;amp; nav.querySelector(&#039;.portal-category-svg&#039;)) {&lt;br /&gt;
            publishSeams(calculateGeometry(width));&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        result = buildSvg(width);&lt;br /&gt;
        nav.__frameWidth = width;&lt;br /&gt;
        clearNode(nav);&lt;br /&gt;
        nav.appendChild(result.svg);&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        publishSeams(result.geometry);&lt;br /&gt;
&lt;br /&gt;
        if (window.ResizeObserver &amp;amp;&amp;amp; !mount.__categoryNavObserver) {&lt;br /&gt;
            mount.__categoryNavObserver = new window.ResizeObserver(function () {&lt;br /&gt;
                schedule(mount);&lt;br /&gt;
            });&lt;br /&gt;
            mount.__categoryNavObserver.observe(mount);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var mounts = [];&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;)) mounts.push(scope);&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;), function (mount) {&lt;br /&gt;
            if (mounts.indexOf(mount) === -1) mounts.push(mount);&lt;br /&gt;
        });&lt;br /&gt;
        mounts.forEach(function (mount) {&lt;br /&gt;
            if (renderMount(mount)) rendered += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { renderAll(document); });&lt;br /&gt;
    } else {&lt;br /&gt;
        renderAll(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            schedule(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () { schedule(document); });&lt;br /&gt;
&lt;br /&gt;
    window.CategoryNav = {&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderAll:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
    window.CLBI = window.CLBI || {};&lt;br /&gt;
    window.CLBI.categoryNav = {&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4675</id>
		<title>미디어위키:BottomGuideNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4675"/>
		<updated>2026-07-17T04:00:18Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-pixel-stack-20260717 / js/BottomGuideNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
BottomGuideNav&lt;br /&gt;
대문 하단 「길라잡이」 SVG 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
상단 CategoryNav와 분리해 로드한다.&lt;br /&gt;
FrameGeometry.js가 공유한 설정–프로젝트–기업 및 공동체 seam을 사용하며,&lt;br /&gt;
상단 SVG나 화면 rect를 다시 읽지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var COMPONENT = &#039;category-guide-nav&#039;;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * FrameGeometry(window.CLBI.frame) 의존은 반드시 렌더 시점에 지연 접근한다.&lt;br /&gt;
     * 모듈 로드 순서상 이 파일이 FrameGeometry.js보다 먼저 실행될 수 있으므로,&lt;br /&gt;
     * 최상단에서 FRAME_API.FRAME 을 즉시 읽으면 undefined 참조로 파일 전체가 죽는다.&lt;br /&gt;
     * (다른 프레임 모듈들과 동일하게 geometryApi() 함수를 통해 접근한다.)&lt;br /&gt;
     */&lt;br /&gt;
    function geometryApi() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* 세로 규격은 매 렌더 시 FrameGeometry 상수에서 새로 파생한다. */&lt;br /&gt;
    function guideDims() {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var f = api.FRAME;&lt;br /&gt;
        var d = api.DIMENSIONS;&lt;br /&gt;
        var top = d.WELL_TOP;&lt;br /&gt;
        var bottom = d.ROW_HEIGHT - d.WELL2_BOTTOM;&lt;br /&gt;
        var height = d.BUTTON_TOTAL;&lt;br /&gt;
        return {&lt;br /&gt;
            HEIGHT:height,&lt;br /&gt;
            TOP_FRAME:top,&lt;br /&gt;
            BOTTOM_FRAME:bottom,&lt;br /&gt;
            GAP:f.GAP,&lt;br /&gt;
            ROW_HEIGHT:d.ROW_HEIGHT,&lt;br /&gt;
            BUTTON_TOP:d.BUTTON_EDGE_TOP,&lt;br /&gt;
            BUTTON_BOTTOM:d.BUTTON_EDGE_BOTTOM,&lt;br /&gt;
            COMPACT_HEIGHT:height,&lt;br /&gt;
            COMPACT_TOP_FRAME:top,&lt;br /&gt;
            COMPACT_BOTTOM_FRAME:bottom&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var retryTimer = 0;&lt;br /&gt;
    var geometryWaitTimer = 0;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var guideLayoutAnimationFrame = 0;&lt;br /&gt;
    var observer = null;&lt;br /&gt;
    var manifestoIntroPromise = null;&lt;br /&gt;
    var MANIFESTO_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var MANIFESTO_INTRO_VERSION = &#039;20260713-divider-equal-gap-005&#039;;&lt;br /&gt;
    var GUIDE_DUMMY_ITEMS = [&lt;br /&gt;
        { key: &#039;start&#039;, label: &#039;처음 오셨다면&#039; },&lt;br /&gt;
        { key: &#039;world&#039;, label: &#039;세계관&#039; },&lt;br /&gt;
        { key: &#039;era&#039;, label: &#039;시대&#039; },&lt;br /&gt;
        { key: &#039;people&#039;, label: &#039;인물&#039; },&lt;br /&gt;
        { key: &#039;anecdote&#039;, label: &#039;에넥도트&#039; },&lt;br /&gt;
        { key: &#039;participate&#039;, label: &#039;참여 안내&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function revealManifesto(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        var portal = scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;) || document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var copy;&lt;br /&gt;
&lt;br /&gt;
        /* 이전 캐시에서 남은 전역 잠금을 즉시 해제한다. */&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-pending&#039;);&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-active&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-prepared&#039;);&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-running&#039;);&lt;br /&gt;
        title = portal.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        copy = portal.querySelector(&#039;.main-manifesto-copy&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (title) {&lt;br /&gt;
            title.style.opacity = &#039;1&#039;;&lt;br /&gt;
            title.style.borderBottom = &#039;0&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (copy) copy.style.visibility = &#039;visible&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line&#039;), function (line) {&lt;br /&gt;
            line.style.height = &#039;26px&#039;;&lt;br /&gt;
            line.style.maxHeight = &#039;26px&#039;;&lt;br /&gt;
            line.style.opacity = &#039;1&#039;;&lt;br /&gt;
            line.style.overflow = &#039;visible&#039;;&lt;br /&gt;
            line.style.clipPath = &#039;inset(0 0 0 0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line-inner&#039;), function (inner) {&lt;br /&gt;
            inner.style.opacity = &#039;1&#039;;&lt;br /&gt;
            inner.style.transform = &#039;translateY(0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifestoIntro(root) {&lt;br /&gt;
        if (&lt;br /&gt;
            window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
            window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
            typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(window.ManifestoIntro.init(root || document)).catch(function () {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    return false;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                revealManifesto(root);&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifestoIntroPromise) return manifestoIntroPromise;&lt;br /&gt;
&lt;br /&gt;
        manifestoIntroPromise = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                if (!ok) {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    manifestoIntroPromise = null;&lt;br /&gt;
                }&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            script.src = &#039;/index.php?title=&#039; + encodeURIComponent(&#039;MediaWiki:ManifestoIntro.js&#039;) +&lt;br /&gt;
                &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&amp;amp;v=&#039; + encodeURIComponent(MANIFESTO_INTRO_VERSION) +&lt;br /&gt;
                &#039;&amp;amp;_manifestoFresh=&#039; + encodeURIComponent(String(Date.now()));&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-manifesto-intro-script&#039;, &#039;1&#039;);&lt;br /&gt;
            script.onload = function () {&lt;br /&gt;
                if (&lt;br /&gt;
                    window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
                    window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
                    typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    Promise.resolve(window.ManifestoIntro.init(root || document))&lt;br /&gt;
                        .then(function () { settle(true); })&lt;br /&gt;
                        .catch(function () { settle(false); });&lt;br /&gt;
                } else {&lt;br /&gt;
                    settle(false);&lt;br /&gt;
                }&lt;br /&gt;
            };&lt;br /&gt;
            script.onerror = function () {&lt;br /&gt;
                settle(false);&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, MANIFESTO_LOAD_TIMEOUT_MS);&lt;br /&gt;
&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return manifestoIntroPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    revealManifesto(document);&lt;br /&gt;
&lt;br /&gt;
    function createSvgElement(name) {&lt;br /&gt;
        return geometryApi().create(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildWikiPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) {&lt;br /&gt;
            return window.buildWikiPath(title);&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPolygon(className, points) {&lt;br /&gt;
        return geometryApi().polygon(className, points);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createLine(className, start, end) {&lt;br /&gt;
        return geometryApi().line(className, start, end);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createRect(className, x, y, width, height) {&lt;br /&gt;
        return geometryApi().rect(className, x, y, width, height);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function centerOfPoints(points) {&lt;br /&gt;
        return [geometryApi().centerX(points), Math.round((points[0][1] + points[2][1]) / 2)];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function shiftY(points, amount) {&lt;br /&gt;
        return points.map(function (point) {&lt;br /&gt;
            return [Math.round(point[0]), Math.round(point[1] + amount)];&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeGuideGeometry(height, topFrame, bottomFrame, sideFrameProgress) {&lt;br /&gt;
        var gap = geometryApi().FRAME.GAP;&lt;br /&gt;
        height = Math.round(height);&lt;br /&gt;
        topFrame = Math.round(topFrame);&lt;br /&gt;
        bottomFrame = Math.round(bottomFrame);&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            height: height,&lt;br /&gt;
            topFrame: topFrame,&lt;br /&gt;
            bottomFrame: bottomFrame,&lt;br /&gt;
            buttonTop: topFrame + gap,&lt;br /&gt;
            buttonBottom: topFrame + gap + height,&lt;br /&gt;
            rowHeight: topFrame + gap + height + gap + bottomFrame,&lt;br /&gt;
            sideFrameProgress: Math.max(0, Math.min(1, Number(sideFrameProgress || 0)))&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getGuideGeometry(compact) {&lt;br /&gt;
        var d = guideDims();&lt;br /&gt;
        return compact&lt;br /&gt;
            ? makeGuideGeometry(d.COMPACT_HEIGHT, d.COMPACT_TOP_FRAME, d.COMPACT_BOTTOM_FRAME, 1)&lt;br /&gt;
            : makeGuideGeometry(d.HEIGHT, d.TOP_FRAME, d.BOTTOM_FRAME, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function interpolateGuideGeometry(from, to, progress) {&lt;br /&gt;
        function between(start, end) {&lt;br /&gt;
            return start + (end - start) * progress;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return makeGuideGeometry(&lt;br /&gt;
            between(from.height, to.height),&lt;br /&gt;
            between(from.topFrame, to.topFrame),&lt;br /&gt;
            between(from.bottomFrame, to.bottomFrame),&lt;br /&gt;
            between(from.sideFrameProgress, to.sideFrameProgress)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyGuideRowHeight(mount, nav, svg, geometry) {&lt;br /&gt;
        var height = geometry.rowHeight + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        [mount, nav, svg].forEach(function (node) {&lt;br /&gt;
            if (!node || !node.style) return;&lt;br /&gt;
            node.style.setProperty(&#039;height&#039;, height, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;min-height&#039;, height, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;max-height&#039;, height, &#039;important&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestGuideFrame(callback) {&lt;br /&gt;
        if (typeof window.requestAnimationFrame === &#039;function&#039;) {&lt;br /&gt;
            return window.requestAnimationFrame(callback);&lt;br /&gt;
        }&lt;br /&gt;
        return window.setTimeout(function () { callback(Date.now()); }, 16);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function cancelGuideFrame(handle) {&lt;br /&gt;
        if (!handle) return;&lt;br /&gt;
        if (typeof window.cancelAnimationFrame === &#039;function&#039;) {&lt;br /&gt;
            window.cancelAnimationFrame(handle);&lt;br /&gt;
        } else {&lt;br /&gt;
            window.clearTimeout(handle);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function animateGuideLayout(portal, compact) {&lt;br /&gt;
        var from = portal &amp;amp;&amp;amp; portal.GuideNavGeometry&lt;br /&gt;
            ? portal.GuideNavGeometry&lt;br /&gt;
            : getGuideGeometry(!compact);&lt;br /&gt;
        var to = getGuideGeometry(compact);&lt;br /&gt;
        var startedAt = null;&lt;br /&gt;
        var duration = 340;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
        if (guideLayoutAnimationFrame) {&lt;br /&gt;
            cancelGuideFrame(guideLayoutAnimationFrame);&lt;br /&gt;
            guideLayoutAnimationFrame = 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (window.matchMedia &amp;amp;&amp;amp; window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            portal.GuideNavGeometry = to;&lt;br /&gt;
            render(portal, to);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function step(now) {&lt;br /&gt;
            var elapsed;&lt;br /&gt;
            var progress;&lt;br /&gt;
            var eased;&lt;br /&gt;
&lt;br /&gt;
            if (startedAt === null) startedAt = now;&lt;br /&gt;
            elapsed = Math.min(1, (now - startedAt) / duration);&lt;br /&gt;
            eased = 1 - Math.pow(1 - elapsed, 3);&lt;br /&gt;
            progress = interpolateGuideGeometry(from, to, eased);&lt;br /&gt;
            portal.GuideNavGeometry = progress;&lt;br /&gt;
            render(portal, progress);&lt;br /&gt;
&lt;br /&gt;
            if (elapsed &amp;lt; 1) {&lt;br /&gt;
                guideLayoutAnimationFrame = requestGuideFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.GuideNavGeometry = to;&lt;br /&gt;
                guideLayoutAnimationFrame = 0;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        guideLayoutAnimationFrame = requestGuideFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildGuideButton(geometry) {&lt;br /&gt;
        var seams = geometryApi().seams;&lt;br /&gt;
        var leftTop;&lt;br /&gt;
        var rightTop;&lt;br /&gt;
        var leftDir;&lt;br /&gt;
        var rightDir;&lt;br /&gt;
&lt;br /&gt;
        if (!seams || !seams.width) return null;&lt;br /&gt;
        leftTop = geometryApi().slantX(&lt;br /&gt;
            seams.settingsLeftTopX,&lt;br /&gt;
            seams.buttonBottom,&lt;br /&gt;
            seams.settingsLeftDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        rightTop = geometryApi().slantX(&lt;br /&gt;
            seams.corpRightTopX,&lt;br /&gt;
            seams.buttonBottom,&lt;br /&gt;
            seams.corpRightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        leftDir = -seams.settingsLeftDir;&lt;br /&gt;
        rightDir = -seams.corpRightDir;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            leftTopX:leftTop,&lt;br /&gt;
            rightTopX:rightTop,&lt;br /&gt;
            leftDir:leftDir,&lt;br /&gt;
            rightDir:rightDir,&lt;br /&gt;
            points:shiftY(&lt;br /&gt;
                geometryApi().segment(leftTop, rightTop, geometry.height, leftDir, rightDir, 0),&lt;br /&gt;
                geometry.buttonTop&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function guideLayer(button, geometry, amount) {&lt;br /&gt;
        return shiftY(&lt;br /&gt;
            geometryApi().segment(&lt;br /&gt;
                button.leftTopX + amount,&lt;br /&gt;
                button.rightTopX - amount,&lt;br /&gt;
                geometry.height - amount * 2,&lt;br /&gt;
                button.leftDir,&lt;br /&gt;
                button.rightDir,&lt;br /&gt;
                amount % geometryApi().FRAME.SLANT_RISE&lt;br /&gt;
            ),&lt;br /&gt;
            geometry.buttonTop + amount&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function guideLayerRow(button, geometry, amount, layerY) {&lt;br /&gt;
        var row = geometryApi().rowAt(&lt;br /&gt;
            button.leftTopX + amount,&lt;br /&gt;
            button.rightTopX - amount,&lt;br /&gt;
            layerY,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            amount % geometryApi().FRAME.SLANT_RISE,&lt;br /&gt;
            0,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        return row.map(function (point) {&lt;br /&gt;
            return [point[0], point[1] + geometry.buttonTop + amount];&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendContinuousBottomFrame(svg, button, width, geometry) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var frame = api.FRAME;&lt;br /&gt;
        var group = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var leftTarget = button.points[0][0] - geometry.buttonTop;&lt;br /&gt;
        var rightTarget = button.points[1][0] + geometry.buttonTop;&lt;br /&gt;
        var leftBase = leftTarget - api.slantX(0, geometry.buttonTop, button.leftDir, 0);&lt;br /&gt;
        var rightBase = rightTarget - api.slantX(0, geometry.buttonTop, button.rightDir, 0);&lt;br /&gt;
        var outer = api.segment(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.rowHeight,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        var well = api.segmentAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.topFrame,&lt;br /&gt;
            geometry.rowHeight - geometry.bottomFrame,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            geometry.topFrame,&lt;br /&gt;
            geometry.topFrame&lt;br /&gt;
        );&lt;br /&gt;
        var surfaceTopRow = api.rowAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            frame.BORDER,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER,&lt;br /&gt;
            frame.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var surfaceBottomRow = api.rowAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.rowHeight - frame.BORDER - frame.BEVEL,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER,&lt;br /&gt;
            frame.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var wellTopRow = api.rowAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.topFrame,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            geometry.topFrame,&lt;br /&gt;
            geometry.topFrame&lt;br /&gt;
        );&lt;br /&gt;
        var wellBottomRow = api.rowAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.rowHeight - geometry.bottomFrame - frame.BEVEL,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            geometry.topFrame,&lt;br /&gt;
            geometry.topFrame&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        group.setAttribute(&#039;class&#039;, &#039;portal-guide-arch-frame&#039;);&lt;br /&gt;
        group.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-guide-arch-outer&#039;, outer));&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-guide-arch-well&#039;, well));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[3], outer[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[0], outer[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[1], outer[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-highlight&#039;, surfaceTopRow[0], surfaceTopRow[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-highlight&#039;, surfaceTopRow[1], surfaceBottomRow[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-shadow&#039;, surfaceBottomRow[0], surfaceTopRow[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-highlight&#039;, wellTopRow[0], wellTopRow[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-highlight&#039;, wellTopRow[1], wellBottomRow[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-shadow&#039;, wellBottomRow[0], wellTopRow[0]));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyPanel(portal) {&lt;br /&gt;
        var panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        var manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) {&lt;br /&gt;
                    topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                } else {&lt;br /&gt;
                    topMount.parentNode.appendChild(panel);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) {&lt;br /&gt;
            well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            panel: panel,&lt;br /&gt;
            well: well&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBodyScreenHidden(screen, hidden) {&lt;br /&gt;
        if (!screen) return;&lt;br /&gt;
&lt;br /&gt;
        screen.classList.toggle(&#039;is-active&#039;, !hidden);&lt;br /&gt;
        screen.hidden = hidden;&lt;br /&gt;
        screen.setAttribute(&#039;aria-hidden&#039;, hidden ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateGuideButtonState(portal, active) {&lt;br /&gt;
        var guideActive = active === &#039;guide&#039;;&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var target = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-anchor&#039;), function (anchor) {&lt;br /&gt;
            anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
            anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            anchor.setAttribute(&#039;data-main-body-target&#039;, target);&lt;br /&gt;
            anchor.removeAttribute(&#039;aria-current&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-label&#039;), function (text) {&lt;br /&gt;
            text.textContent = label;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-nav&#039;), function (nav) {&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureGuideDummyMenu(guide) {&lt;br /&gt;
        var menu;&lt;br /&gt;
&lt;br /&gt;
        if (!guide) return null;&lt;br /&gt;
&lt;br /&gt;
        menu = guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
        if (menu) return menu;&lt;br /&gt;
&lt;br /&gt;
        menu = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        menu.className = &#039;main-guide-menu&#039;;&lt;br /&gt;
        menu.setAttribute(&#039;data-guide-dummy-menu&#039;, &#039;1&#039;);&lt;br /&gt;
        menu.setAttribute(&#039;aria-label&#039;, &#039;길라잡이 항목 예시&#039;);&lt;br /&gt;
&lt;br /&gt;
        GUIDE_DUMMY_ITEMS.forEach(function (item, index) {&lt;br /&gt;
            var wrapper = document.createElement(&#039;div&#039;);&lt;br /&gt;
            var button = document.createElement(&#039;button&#039;);&lt;br /&gt;
            var number = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var arrow = document.createElement(&#039;span&#039;);&lt;br /&gt;
&lt;br /&gt;
            wrapper.className = &#039;main-guide-menu-item&#039;;&lt;br /&gt;
            wrapper.style.setProperty(&#039;--guide-menu-order&#039;, String(index));&lt;br /&gt;
&lt;br /&gt;
            button.type = &#039;button&#039;;&lt;br /&gt;
            button.className = &#039;main-guide-menu-button&#039;;&lt;br /&gt;
            button.setAttribute(&#039;data-guide-dummy-button&#039;, item.key);&lt;br /&gt;
            button.setAttribute(&#039;aria-label&#039;, item.label + &#039; (더미 버튼)&#039;);&lt;br /&gt;
&lt;br /&gt;
            number.className = &#039;main-guide-menu-number&#039;;&lt;br /&gt;
            number.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            number.textContent = String(index + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
            label.className = &#039;main-guide-menu-label&#039;;&lt;br /&gt;
            label.textContent = item.label;&lt;br /&gt;
&lt;br /&gt;
            arrow.className = &#039;main-guide-menu-arrow&#039;;&lt;br /&gt;
            arrow.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            arrow.textContent = &#039;›&#039;;&lt;br /&gt;
&lt;br /&gt;
            button.appendChild(number);&lt;br /&gt;
            button.appendChild(label);&lt;br /&gt;
            button.appendChild(arrow);&lt;br /&gt;
            button.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
            });&lt;br /&gt;
            wrapper.appendChild(button);&lt;br /&gt;
            menu.appendChild(wrapper);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        guide.insertBefore(menu, guide.firstChild);&lt;br /&gt;
        return menu;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function restartGuideMenuAnimation(guide) {&lt;br /&gt;
        if (!guide || !guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        /* 같은 화면에 재진입해도 첫 프레임부터 순차 진입을 다시 시작한다. */&lt;br /&gt;
        void guide.offsetWidth;&lt;br /&gt;
        guide.classList.add(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyScreens(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var manifesto = layout.well.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
        var guide = layout.well.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;);&lt;br /&gt;
        var panels;&lt;br /&gt;
        var active = layout.well.getAttribute(&#039;data-active-main-body-screen&#039;) ||&lt;br /&gt;
            portal.getAttribute(&#039;data-main-body-screen&#039;) ||&lt;br /&gt;
            &#039;manifesto&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (manifesto) {&lt;br /&gt;
            manifesto.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            manifesto.setAttribute(&#039;data-main-body-screen&#039;, &#039;manifesto&#039;);&lt;br /&gt;
            if (!manifesto.id) manifesto.id = &#039;main-manifesto-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!guide) {&lt;br /&gt;
            guide = document.createElement(&#039;section&#039;);&lt;br /&gt;
            guide.className = &#039;main-body-screen main-guide-screen&#039;;&lt;br /&gt;
            guide.setAttribute(&#039;data-main-body-screen&#039;, &#039;guide&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;data-main-guide-screen-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;aria-label&#039;, &#039;길라잡이&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;role&#039;, &#039;region&#039;);&lt;br /&gt;
            guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
            layout.well.appendChild(guide);&lt;br /&gt;
        } else {&lt;br /&gt;
            guide.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            guide.classList.add(&#039;main-guide-screen&#039;);&lt;br /&gt;
            if (!guide.id) guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        ensureGuideDummyMenu(guide);&lt;br /&gt;
&lt;br /&gt;
        panels = Array.prototype.slice.call(&lt;br /&gt;
            layout.well.querySelectorAll(&#039;.main-body-screen[data-main-body-screen]&#039;)&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (active !== &#039;guide&#039;) active = &#039;manifesto&#039;;&lt;br /&gt;
        if (!panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === active;&lt;br /&gt;
        })) {&lt;br /&gt;
            active = &#039;manifesto&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(&lt;br /&gt;
                panel,&lt;br /&gt;
                panel.getAttribute(&#039;data-main-body-screen&#039;) !== active&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        layout.well.setAttribute(&#039;data-main-body-screen-controller-ready&#039;, &#039;1&#039;);&lt;br /&gt;
        layout.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; !guide.classList.contains(&#039;is-guide-menu-entering&#039;)) {&lt;br /&gt;
            restartGuideMenuAnimation(guide);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            well: layout.well,&lt;br /&gt;
            panels: panels,&lt;br /&gt;
            manifesto: manifesto,&lt;br /&gt;
            guide: guide,&lt;br /&gt;
            active: active&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBodyScreen(portal, screenName) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var previous;&lt;br /&gt;
        var active;&lt;br /&gt;
        var event;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        screenName = screenName === &#039;guide&#039; ? &#039;guide&#039; : &#039;manifesto&#039;;&lt;br /&gt;
        if (!screens.panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === screenName;&lt;br /&gt;
        })) return false;&lt;br /&gt;
&lt;br /&gt;
        previous = screens.active;&lt;br /&gt;
        active = screenName;&lt;br /&gt;
&lt;br /&gt;
        screens.panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(&lt;br /&gt;
                panel,&lt;br /&gt;
                panel.getAttribute(&#039;data-main-body-screen&#039;) !== active&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        screens.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; previous !== active) {&lt;br /&gt;
            restartGuideMenuAnimation(screens.guide);&lt;br /&gt;
        } else if (active !== &#039;guide&#039; &amp;amp;&amp;amp; screens.guide) {&lt;br /&gt;
            screens.guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            animateGuideLayout(portal, active === &#039;guide&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            try {&lt;br /&gt;
                event = new CustomEvent(&#039;mainbodyscreenchange&#039;, {&lt;br /&gt;
                    bubbles: true,&lt;br /&gt;
                    detail: { screen: active, previous: previous }&lt;br /&gt;
                });&lt;br /&gt;
                portal.dispatchEvent(event);&lt;br /&gt;
            } catch (error) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function toggleBodyScreen(portal) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        target = screens.active === &#039;guide&#039; ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        return activateBodyScreen(portal, target);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleGuideScreenCapture(event) {&lt;br /&gt;
        var target;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var portal;&lt;br /&gt;
&lt;br /&gt;
        if (!event || event.__mainGuideScreenHandled) return;&lt;br /&gt;
        if (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        target = event.target;&lt;br /&gt;
        anchor = target &amp;amp;&amp;amp; target.closest&lt;br /&gt;
            ? target.closest(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)&lt;br /&gt;
            : null;&lt;br /&gt;
        if (!anchor) return;&lt;br /&gt;
&lt;br /&gt;
        portal = anchor.closest ? anchor.closest(&#039;.main-portal&#039;) : null;&lt;br /&gt;
        if (!portal || !toggleBodyScreen(portal)) return;&lt;br /&gt;
&lt;br /&gt;
        event.__mainGuideScreenHandled = true;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) {&lt;br /&gt;
            event.stopImmediatePropagation();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    이전 실행본의 ResizeObserver가 오래된 문서 링크를 다시 그려도&lt;br /&gt;
    전역 SPA 링크 처리보다 먼저 화면 전환으로 고정한다.&lt;br /&gt;
    */&lt;br /&gt;
    if (window.BottomGuideNavScreenCaptureHandler) {&lt;br /&gt;
        document.removeEventListener(&#039;click&#039;, window.BottomGuideNavScreenCaptureHandler, true);&lt;br /&gt;
    }&lt;br /&gt;
    window.BottomGuideNavScreenCaptureHandler = handleGuideScreenCapture;&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleGuideScreenCapture, true);&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        ensureBodyScreens(portal);&lt;br /&gt;
&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
            mount.setAttribute(&#039;data-bottom-guide-generated&#039;, &#039;1&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        길라잡이는 우물 안의 독립 버튼이 아니라&lt;br /&gt;
        main-body-panel 하단 프레임에 융합된 돌출부다.&lt;br /&gt;
        */&lt;br /&gt;
        if (mount.parentNode !== layout.panel) {&lt;br /&gt;
            layout.panel.appendChild(mount);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(portal, suppliedGeometry) {&lt;br /&gt;
        /*&lt;br /&gt;
         * FrameGeometry(window.CLBI.frame)가 아직 로드되지 않았으면 렌더를 미룬다.&lt;br /&gt;
         * 이 파일이 FrameGeometry.js보다 먼저 실행될 수 있으므로, 준비되지 않은&lt;br /&gt;
         * 상태에서 geometryApi().readWidth(...) 등을 호출하면 undefined 참조로 죽는다.&lt;br /&gt;
         * 준비될 때까지 짧게 재시도한다.&lt;br /&gt;
         */&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        if (!api || !api.FRAME || typeof api.readWidth !== &#039;function&#039;) {&lt;br /&gt;
            window.clearTimeout(geometryWaitTimer);&lt;br /&gt;
            geometryWaitTimer = window.setTimeout(function () {&lt;br /&gt;
                geometryWaitTimer = 0;&lt;br /&gt;
                render(portal, suppliedGeometry);&lt;br /&gt;
            }, 50);&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var mount = ensureMount(portal);&lt;br /&gt;
        var width = api.readWidth(mount);&lt;br /&gt;
        var guideActive = portal.getAttribute(&#039;data-main-body-screen&#039;) === &#039;guide&#039;;&lt;br /&gt;
        var geometry = suppliedGeometry || portal.GuideNavGeometry || getGuideGeometry(guideActive);&lt;br /&gt;
&lt;br /&gt;
        if (!width) return false;&lt;br /&gt;
&lt;br /&gt;
        portal.GuideNavGeometry = geometry;&lt;br /&gt;
&lt;br /&gt;
        var button = buildGuideButton(geometry);&lt;br /&gt;
        var points;&lt;br /&gt;
        var surfacePoints;&lt;br /&gt;
        var topInset;&lt;br /&gt;
        var bottomInset;&lt;br /&gt;
        var center;&lt;br /&gt;
&lt;br /&gt;
        if (!button) return false;&lt;br /&gt;
        points = button.points;&lt;br /&gt;
        surfacePoints = guideLayer(button, geometry, api.FRAME.BORDER);&lt;br /&gt;
        topInset = guideLayerRow(button, geometry, api.FRAME.BORDER, 0);&lt;br /&gt;
        bottomInset = guideLayerRow(&lt;br /&gt;
            button,&lt;br /&gt;
            geometry,&lt;br /&gt;
            api.FRAME.BORDER,&lt;br /&gt;
            geometry.height - api.FRAME.BORDER * 2 - api.FRAME.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
        center = centerOfPoints(surfacePoints);&lt;br /&gt;
        var nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        var svg = createSvgElement(&#039;svg&#039;);&lt;br /&gt;
        var anchor = createSvgElement(&#039;a&#039;);&lt;br /&gt;
        var buttonGeometry = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var targetScreen = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
        var text = createSvgElement(&#039;text&#039;);&lt;br /&gt;
        var outer = createPolygon(&#039;portal-cat-edge&#039;, points);&lt;br /&gt;
        var surface = createPolygon(&#039;portal-cat-surface&#039;, surfacePoints);&lt;br /&gt;
        var hit = createPolygon(&#039;portal-cat-hit&#039;, points);&lt;br /&gt;
&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-guide-nav is-continuous-weld&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
&lt;br /&gt;
        svg.setAttribute(&#039;class&#039;, &#039;portal-category-svg portal-guide-svg&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + width + &#039; &#039; + geometry.rowHeight);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, geometry.rowHeight);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        appendContinuousBottomFrame(svg, button, width, geometry);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;class&#039;, &#039;portal-cat-anchor portal-guide-anchor&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        anchor.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, &#039;guide&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-main-body-target&#039;, targetScreen);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-controls&#039;, &#039;main-manifesto-screen main-guide-screen&#039;);&lt;br /&gt;
        buttonGeometry.setAttribute(&#039;class&#039;, &#039;portal-guide-button-geometry&#039;);&lt;br /&gt;
&lt;br /&gt;
        anchor.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            if (&lt;br /&gt;
                event &amp;amp;&amp;amp;&lt;br /&gt;
                (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (toggleBodyScreen(portal)) {&lt;br /&gt;
                if (event) event.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        outer.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        surface.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;class&#039;, &#039;portal-cat-label portal-guide-label&#039;);&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, Math.round(center[0]));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, Math.round((geometry.buttonTop + geometry.buttonBottom) / 2) + 1);&lt;br /&gt;
        text.setAttribute(&#039;font-size&#039;, &#039;12&#039;);&lt;br /&gt;
        text.setAttribute(&#039;font-weight&#039;, &#039;700&#039;);&lt;br /&gt;
        text.setAttribute(&#039;text-anchor&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;dominant-baseline&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        text.textContent = label;&lt;br /&gt;
&lt;br /&gt;
        buttonGeometry.appendChild(outer);&lt;br /&gt;
        buttonGeometry.appendChild(surface);&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-top&#039;,&lt;br /&gt;
            topInset[0],&lt;br /&gt;
            topInset[1]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-right&#039;,&lt;br /&gt;
            topInset[1],&lt;br /&gt;
            bottomInset[1]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-bottom&#039;,&lt;br /&gt;
            bottomInset[1],&lt;br /&gt;
            bottomInset[0]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-left&#039;,&lt;br /&gt;
            bottomInset[0],&lt;br /&gt;
            topInset[0]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(hit);&lt;br /&gt;
        anchor.appendChild(buttonGeometry);&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        svg.appendChild(anchor);&lt;br /&gt;
        nav.appendChild(svg);&lt;br /&gt;
&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        applyGuideRowHeight(mount, nav, svg, geometry);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-width&#039;, String(width));&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findPortal(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, attempt) {&lt;br /&gt;
        window.clearTimeout(retryTimer);&lt;br /&gt;
&lt;br /&gt;
        retryTimer = window.setTimeout(function () {&lt;br /&gt;
            var portal = findPortal(root);&lt;br /&gt;
&lt;br /&gt;
            if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
            if (!render(portal) &amp;amp;&amp;amp; attempt &amp;lt; 20) {&lt;br /&gt;
                schedule(root, attempt + 1);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (observer) observer.disconnect();&lt;br /&gt;
&lt;br /&gt;
            if (window.ResizeObserver) {&lt;br /&gt;
                observer = new ResizeObserver(function () {&lt;br /&gt;
                    window.clearTimeout(resizeTimer);&lt;br /&gt;
                    resizeTimer = window.setTimeout(function () {&lt;br /&gt;
                        render(portal);&lt;br /&gt;
                    }, 60);&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                observer.observe(portal);&lt;br /&gt;
                var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
                var guideMount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
                if (topMount) observer.observe(topMount);&lt;br /&gt;
                if (guideMount) observer.observe(guideMount);&lt;br /&gt;
            }&lt;br /&gt;
        }, attempt ? 60 : 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot(root) {&lt;br /&gt;
        var scope = root || document;&lt;br /&gt;
        schedule(scope, 0);&lt;br /&gt;
        loadManifestoIntro(scope);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {&lt;br /&gt;
            boot(document);&lt;br /&gt;
        }, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* 일반 링크 진입과 새로고침의 최종 로드 시점도 명시적으로 보장한다. */&lt;br /&gt;
    window.addEventListener(&#039;load&#039;, function () {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }, { once: true });&lt;br /&gt;
&lt;br /&gt;
    /* bfcache 복원과 브라우저 뒤로/앞으로 이동에서도 새 DOM만 초기화한다. */&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, function () {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            boot($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BottomGuideNav = {&lt;br /&gt;
        version: &#039;20260717-pixel-stack-042&#039;,&lt;br /&gt;
        render: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? render(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        activate: function (screenName) {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? activateBodyScreen(portal, screenName) : false;&lt;br /&gt;
        },&lt;br /&gt;
        toggle: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? toggleBodyScreen(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        status: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            var mount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
            return {&lt;br /&gt;
                loaded: true,&lt;br /&gt;
                portal: !!portal,&lt;br /&gt;
                mount: !!mount,&lt;br /&gt;
                rendered: !!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-svg&#039;)),&lt;br /&gt;
                generated: !!(mount &amp;amp;&amp;amp; mount.getAttribute(&#039;data-bottom-guide-generated&#039;)),&lt;br /&gt;
                panel: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;)),&lt;br /&gt;
                well: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-well&#039;)),&lt;br /&gt;
                insidePanel: !!(mount &amp;amp;&amp;amp; mount.parentNode &amp;amp;&amp;amp; mount.parentNode.classList.contains(&#039;main-body-panel&#039;)),&lt;br /&gt;
                welded: !!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-nav.is-continuous-weld&#039;)),&lt;br /&gt;
                activeScreen: portal ? portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039; : &#039;&#039;,&lt;br /&gt;
                guideScreen: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;)),&lt;br /&gt;
                width: mount ? mount.getAttribute(&#039;data-bottom-guide-width&#039;) || &#039;&#039; : &#039;&#039;&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4674</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4674"/>
		<updated>2026-07-17T04:00:17Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-pixel-stack-20260717 / css/MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:42px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
FrameGeometry.js가 공유한 설정 왼쪽 seam과 기업 및 공동체 오른쪽 seam을&lt;br /&gt;
같은 정수 좌표계에서 연장한다. 상단 프레임 하단 9px 안으로 겹쳐 접합하며,&lt;br /&gt;
표시 높이는 공통 ROW_HEIGHT 42px만 사용한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-outline {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:10%;&lt;br /&gt;
    right:auto;&lt;br /&gt;
    width:80%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 외곽 테두리를 포함한 42px 정수 단면을 사용한다. */&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단·하단 총 42px:&lt;br /&gt;
외곽1 + 프레임면7 + 우물2 + 버튼(테두리1 + 표면20 + 테두리1) + 우물2 + 프레임면7 + 외곽1&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
사선 프레임 단일 정수 좌표계 / 20260717&lt;br /&gt;
FrameGeometry.js의 1+7 / 2 / 1+20+1 / 2 / 7+1 정수 단면을 그대로 표시한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav,&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-highlight,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:focus .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame,&lt;br /&gt;
.main-portal .portal-section-frame,&lt;br /&gt;
.main-portal .portal-category-pillar,&lt;br /&gt;
.main-portal .portal-section-button-geometry,&lt;br /&gt;
.main-portal .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-hit {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 길라잡이도 같은 42px 정수 단면과 반대 사선을 사용한다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    stroke:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* main-body-panel의 1px 좌우 외곽선을 보정해 상단 seam 좌표폭과 일치시킨다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    left:-1px !important;&lt;br /&gt;
    right:-1px !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:FrameGeometry.js&amp;diff=4673</id>
		<title>미디어위키:FrameGeometry.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:FrameGeometry.js&amp;diff=4673"/>
		<updated>2026-07-17T03:09:11Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: 새 문서: /* ========================================= FrameGeometry 대문 사선 프레임 공통 정수 좌표계 ========================================= */  (function (w, document) {     &amp;#039;use strict&amp;#039;;      var SVG_NS = &amp;#039;http://www.w3.org/2000/svg&amp;#039;;     var FRAME = {         BORDER:1,         BEVEL:1,         SLANT_RISE:2,         SLANT_RUN:1,         BUTTON_FACE:20,         FRAME_FACE:7,         GAP:2,         COLORS:{             border:&amp;#039;#050505&amp;#039;,             surface:&amp;#039;#1d1d1d&amp;#039;,...&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
FrameGeometry&lt;br /&gt;
대문 사선 프레임 공통 정수 좌표계&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (w, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    var FRAME = {&lt;br /&gt;
        BORDER:1,&lt;br /&gt;
        BEVEL:1,&lt;br /&gt;
        SLANT_RISE:2,&lt;br /&gt;
        SLANT_RUN:1,&lt;br /&gt;
        BUTTON_FACE:20,&lt;br /&gt;
        FRAME_FACE:7,&lt;br /&gt;
        GAP:2,&lt;br /&gt;
        COLORS:{&lt;br /&gt;
            border:&#039;#050505&#039;,&lt;br /&gt;
            surface:&#039;#1d1d1d&#039;,&lt;br /&gt;
            well:&#039;#080808&#039;,&lt;br /&gt;
            highlight:&#039;#555555&#039;,&lt;br /&gt;
            shadow:&#039;#101010&#039;,&lt;br /&gt;
            label:&#039;#e2e2e2&#039;&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    var DIMENSIONS = {&lt;br /&gt;
        TOP_FRAME:FRAME.FRAME_FACE,&lt;br /&gt;
        BUTTON_TOP:FRAME.FRAME_FACE + FRAME.GAP,&lt;br /&gt;
        BUTTON_BOTTOM:FRAME.FRAME_FACE + FRAME.GAP + FRAME.BUTTON_FACE,&lt;br /&gt;
        BOTTOM_FRAME:FRAME.FRAME_FACE,&lt;br /&gt;
        ROW_HEIGHT:FRAME.FRAME_FACE + FRAME.GAP + FRAME.BUTTON_FACE + FRAME.GAP + FRAME.FRAME_FACE,&lt;br /&gt;
        OVERLAP:FRAME.FRAME_FACE + FRAME.GAP&lt;br /&gt;
    };&lt;br /&gt;
    var listeners = [];&lt;br /&gt;
&lt;br /&gt;
    DIMENSIONS.BOTTOM_START_ABS_Y = DIMENSIONS.ROW_HEIGHT - DIMENSIONS.OVERLAP;&lt;br /&gt;
&lt;br /&gt;
    function integer(value) {&lt;br /&gt;
        return Math.round(Number(value) || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function snapWidth(raw) {&lt;br /&gt;
        return Math.floor(Number(raw) || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function slantX(xTop, y, dir, phase) {&lt;br /&gt;
        phase = phase || 0;&lt;br /&gt;
        return xTop + dir * Math.floor((y + phase) / FRAME.SLANT_RISE) * FRAME.SLANT_RUN;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function segment(leftTopX, rightTopX, height, leftDir, rightDir, phase) {&lt;br /&gt;
        return [&lt;br /&gt;
            [integer(leftTopX), 0],&lt;br /&gt;
            [integer(rightTopX), 0],&lt;br /&gt;
            [integer(slantX(rightTopX, height, rightDir, phase)), integer(height)],&lt;br /&gt;
            [integer(slantX(leftTopX, height, leftDir, phase)), integer(height)]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function segmentAt(leftTopX, rightTopX, topY, bottomY, leftDir, rightDir, phase, leftInset, rightInset) {&lt;br /&gt;
        leftInset = integer(leftInset || 0);&lt;br /&gt;
        rightInset = integer(rightInset || 0);&lt;br /&gt;
        return [&lt;br /&gt;
            [integer(slantX(leftTopX, topY, leftDir, phase) + leftInset), integer(topY)],&lt;br /&gt;
            [integer(slantX(rightTopX, topY, rightDir, phase) - rightInset), integer(topY)],&lt;br /&gt;
            [integer(slantX(rightTopX, bottomY, rightDir, phase) - rightInset), integer(bottomY)],&lt;br /&gt;
            [integer(slantX(leftTopX, bottomY, leftDir, phase) + leftInset), integer(bottomY)]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function pts(points) {&lt;br /&gt;
        return points.map(function (point) {&lt;br /&gt;
            return integer(point[0]) + &#039;,&#039; + integer(point[1]);&lt;br /&gt;
        }).join(&#039; &#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function create(name, className) {&lt;br /&gt;
        var node = document.createElementNS(SVG_NS, name);&lt;br /&gt;
        if (className) node.setAttribute(&#039;class&#039;, className);&lt;br /&gt;
        node.setAttribute(&#039;shape-rendering&#039;, &#039;crispEdges&#039;);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function polygon(className, points) {&lt;br /&gt;
        var node = create(&#039;polygon&#039;, className);&lt;br /&gt;
        node.setAttribute(&#039;points&#039;, pts(points));&lt;br /&gt;
        node.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function line(className, start, end) {&lt;br /&gt;
        var node = create(&#039;line&#039;, className);&lt;br /&gt;
        node.setAttribute(&#039;x1&#039;, integer(start[0]));&lt;br /&gt;
        node.setAttribute(&#039;y1&#039;, integer(start[1]));&lt;br /&gt;
        node.setAttribute(&#039;x2&#039;, integer(end[0]));&lt;br /&gt;
        node.setAttribute(&#039;y2&#039;, integer(end[1]));&lt;br /&gt;
        node.setAttribute(&#039;vector-effect&#039;, &#039;non-scaling-stroke&#039;);&lt;br /&gt;
        node.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rect(className, x, y, width, height) {&lt;br /&gt;
        var node = create(&#039;rect&#039;, className);&lt;br /&gt;
        node.setAttribute(&#039;x&#039;, integer(x));&lt;br /&gt;
        node.setAttribute(&#039;y&#039;, integer(y));&lt;br /&gt;
        node.setAttribute(&#039;width&#039;, Math.max(0, integer(width)));&lt;br /&gt;
        node.setAttribute(&#039;height&#039;, Math.max(0, integer(height)));&lt;br /&gt;
        node.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function centerX(points) {&lt;br /&gt;
        return integer((points[0][0] + points[1][0] + points[2][0] + points[3][0]) / 4);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readWidth(node) {&lt;br /&gt;
        if (!node) return 0;&lt;br /&gt;
        return snapWidth(node.clientWidth || node.offsetWidth || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setSeams(next) {&lt;br /&gt;
        var frame = w.CLBI.frame;&lt;br /&gt;
        var copy = {};&lt;br /&gt;
&lt;br /&gt;
        Object.keys(next || {}).forEach(function (key) {&lt;br /&gt;
            copy[key] = next[key];&lt;br /&gt;
        });&lt;br /&gt;
        copy.revision = integer((frame.seams &amp;amp;&amp;amp; frame.seams.revision) || 0) + 1;&lt;br /&gt;
        frame.seams = copy;&lt;br /&gt;
&lt;br /&gt;
        listeners.slice().forEach(function (listener) {&lt;br /&gt;
            try { listener(copy); } catch (error) {}&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            document.dispatchEvent(new CustomEvent(&#039;framegeometryseams&#039;, { detail:copy }));&lt;br /&gt;
        } catch (error) {}&lt;br /&gt;
&lt;br /&gt;
        if (w.mw &amp;amp;&amp;amp; w.mw.hook) {&lt;br /&gt;
            w.mw.hook(&#039;frame.geometry.seams&#039;).fire(copy);&lt;br /&gt;
        }&lt;br /&gt;
        return copy;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function onSeams(listener) {&lt;br /&gt;
        if (typeof listener !== &#039;function&#039;) return function () {};&lt;br /&gt;
        listeners.push(listener);&lt;br /&gt;
        if (w.CLBI.frame.seams &amp;amp;&amp;amp; w.CLBI.frame.seams.width) {&lt;br /&gt;
            listener(w.CLBI.frame.seams);&lt;br /&gt;
        }&lt;br /&gt;
        return function () {&lt;br /&gt;
            var index = listeners.indexOf(listener);&lt;br /&gt;
            if (index !== -1) listeners.splice(index, 1);&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    w.CLBI = w.CLBI || {};&lt;br /&gt;
    w.CLBI.frame = {&lt;br /&gt;
        FRAME:FRAME,&lt;br /&gt;
        DIMENSIONS:DIMENSIONS,&lt;br /&gt;
        slantX:slantX,&lt;br /&gt;
        segment:segment,&lt;br /&gt;
        segmentAt:segmentAt,&lt;br /&gt;
        pts:pts,&lt;br /&gt;
        create:create,&lt;br /&gt;
        polygon:polygon,&lt;br /&gt;
        line:line,&lt;br /&gt;
        rect:rect,&lt;br /&gt;
        centerX:centerX,&lt;br /&gt;
        snapWidth:snapWidth,&lt;br /&gt;
        readWidth:readWidth,&lt;br /&gt;
        setSeams:setSeams,&lt;br /&gt;
        onSeams:onSeams,&lt;br /&gt;
        seams:{}&lt;br /&gt;
    };&lt;br /&gt;
}(window, document));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4672</id>
		<title>미디어위키:BottomGuideNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4672"/>
		<updated>2026-07-17T02:58:58Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
BottomGuideNav&lt;br /&gt;
대문 하단 「길라잡이」 SVG 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
상단 CategoryNav와 분리해 로드한다.&lt;br /&gt;
FrameGeometry.js가 공유한 설정–프로젝트–기업 및 공동체 seam을 사용하며,&lt;br /&gt;
상단 SVG나 화면 rect를 다시 읽지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var COMPONENT = &#039;category-guide-nav&#039;;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * FrameGeometry(window.CLBI.frame) 의존은 반드시 렌더 시점에 지연 접근한다.&lt;br /&gt;
     * 모듈 로드 순서상 이 파일이 FrameGeometry.js보다 먼저 실행될 수 있으므로,&lt;br /&gt;
     * 최상단에서 FRAME_API.FRAME 을 즉시 읽으면 undefined 참조로 파일 전체가 죽는다.&lt;br /&gt;
     * (다른 프레임 모듈들과 동일하게 geometryApi() 함수를 통해 접근한다.)&lt;br /&gt;
     */&lt;br /&gt;
    function geometryApi() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* 세로 규격은 매 렌더 시 FrameGeometry 상수에서 새로 파생한다. */&lt;br /&gt;
    function guideDims() {&lt;br /&gt;
        var f = geometryApi().FRAME;&lt;br /&gt;
        var top = f.FRAME_FACE;&lt;br /&gt;
        var bottom = f.FRAME_FACE;&lt;br /&gt;
        var height = f.BUTTON_FACE;&lt;br /&gt;
        var gap = f.GAP;&lt;br /&gt;
        return {&lt;br /&gt;
            HEIGHT: height,&lt;br /&gt;
            TOP_FRAME: top,&lt;br /&gt;
            BOTTOM_FRAME: bottom,&lt;br /&gt;
            GAP: gap,&lt;br /&gt;
            ROW_HEIGHT: top + gap + height + gap + bottom,&lt;br /&gt;
            BUTTON_TOP: top + gap,&lt;br /&gt;
            BUTTON_BOTTOM: top + gap + height,&lt;br /&gt;
            COMPACT_HEIGHT: height,&lt;br /&gt;
            COMPACT_TOP_FRAME: top,&lt;br /&gt;
            COMPACT_BOTTOM_FRAME: bottom&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
    var retryTimer = 0;&lt;br /&gt;
    var geometryWaitTimer = 0;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var guideLayoutAnimationFrame = 0;&lt;br /&gt;
    var observer = null;&lt;br /&gt;
    var manifestoIntroPromise = null;&lt;br /&gt;
    var MANIFESTO_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var MANIFESTO_INTRO_VERSION = &#039;20260713-divider-equal-gap-005&#039;;&lt;br /&gt;
    var GUIDE_DUMMY_ITEMS = [&lt;br /&gt;
        { key: &#039;start&#039;, label: &#039;처음 오셨다면&#039; },&lt;br /&gt;
        { key: &#039;world&#039;, label: &#039;세계관&#039; },&lt;br /&gt;
        { key: &#039;era&#039;, label: &#039;시대&#039; },&lt;br /&gt;
        { key: &#039;people&#039;, label: &#039;인물&#039; },&lt;br /&gt;
        { key: &#039;anecdote&#039;, label: &#039;에넥도트&#039; },&lt;br /&gt;
        { key: &#039;participate&#039;, label: &#039;참여 안내&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function revealManifesto(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        var portal = scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;) || document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var copy;&lt;br /&gt;
&lt;br /&gt;
        /* 이전 캐시에서 남은 전역 잠금을 즉시 해제한다. */&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-pending&#039;);&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-active&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-prepared&#039;);&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-running&#039;);&lt;br /&gt;
        title = portal.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        copy = portal.querySelector(&#039;.main-manifesto-copy&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (title) {&lt;br /&gt;
            title.style.opacity = &#039;1&#039;;&lt;br /&gt;
            title.style.borderBottom = &#039;0&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (copy) copy.style.visibility = &#039;visible&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line&#039;), function (line) {&lt;br /&gt;
            line.style.height = &#039;26px&#039;;&lt;br /&gt;
            line.style.maxHeight = &#039;26px&#039;;&lt;br /&gt;
            line.style.opacity = &#039;1&#039;;&lt;br /&gt;
            line.style.overflow = &#039;visible&#039;;&lt;br /&gt;
            line.style.clipPath = &#039;inset(0 0 0 0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line-inner&#039;), function (inner) {&lt;br /&gt;
            inner.style.opacity = &#039;1&#039;;&lt;br /&gt;
            inner.style.transform = &#039;translateY(0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifestoIntro(root) {&lt;br /&gt;
        if (&lt;br /&gt;
            window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
            window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
            typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(window.ManifestoIntro.init(root || document)).catch(function () {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    return false;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                revealManifesto(root);&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifestoIntroPromise) return manifestoIntroPromise;&lt;br /&gt;
&lt;br /&gt;
        manifestoIntroPromise = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                if (!ok) {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    manifestoIntroPromise = null;&lt;br /&gt;
                }&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            script.src = &#039;/index.php?title=&#039; + encodeURIComponent(&#039;MediaWiki:ManifestoIntro.js&#039;) +&lt;br /&gt;
                &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&amp;amp;v=&#039; + encodeURIComponent(MANIFESTO_INTRO_VERSION) +&lt;br /&gt;
                &#039;&amp;amp;_manifestoFresh=&#039; + encodeURIComponent(String(Date.now()));&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-manifesto-intro-script&#039;, &#039;1&#039;);&lt;br /&gt;
            script.onload = function () {&lt;br /&gt;
                if (&lt;br /&gt;
                    window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
                    window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
                    typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    Promise.resolve(window.ManifestoIntro.init(root || document))&lt;br /&gt;
                        .then(function () { settle(true); })&lt;br /&gt;
                        .catch(function () { settle(false); });&lt;br /&gt;
                } else {&lt;br /&gt;
                    settle(false);&lt;br /&gt;
                }&lt;br /&gt;
            };&lt;br /&gt;
            script.onerror = function () {&lt;br /&gt;
                settle(false);&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, MANIFESTO_LOAD_TIMEOUT_MS);&lt;br /&gt;
&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return manifestoIntroPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    revealManifesto(document);&lt;br /&gt;
&lt;br /&gt;
    function createSvgElement(name) {&lt;br /&gt;
        return geometryApi().create(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildWikiPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) {&lt;br /&gt;
            return window.buildWikiPath(title);&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPolygon(className, points) {&lt;br /&gt;
        return geometryApi().polygon(className, points);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createLine(className, start, end) {&lt;br /&gt;
        return geometryApi().line(className, start, end);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createRect(className, x, y, width, height) {&lt;br /&gt;
        return geometryApi().rect(className, x, y, width, height);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function centerOfPoints(points) {&lt;br /&gt;
        return [geometryApi().centerX(points), Math.round((points[0][1] + points[2][1]) / 2)];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function shiftY(points, amount) {&lt;br /&gt;
        return points.map(function (point) {&lt;br /&gt;
            return [Math.round(point[0]), Math.round(point[1] + amount)];&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeGuideGeometry(height, topFrame, bottomFrame, sideFrameProgress) {&lt;br /&gt;
        var gap = geometryApi().FRAME.GAP;&lt;br /&gt;
        height = Math.round(height);&lt;br /&gt;
        topFrame = Math.round(topFrame);&lt;br /&gt;
        bottomFrame = Math.round(bottomFrame);&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            height: height,&lt;br /&gt;
            topFrame: topFrame,&lt;br /&gt;
            bottomFrame: bottomFrame,&lt;br /&gt;
            buttonTop: topFrame + gap,&lt;br /&gt;
            buttonBottom: topFrame + gap + height,&lt;br /&gt;
            rowHeight: topFrame + gap + height + gap + bottomFrame,&lt;br /&gt;
            sideFrameProgress: Math.max(0, Math.min(1, Number(sideFrameProgress || 0)))&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getGuideGeometry(compact) {&lt;br /&gt;
        var d = guideDims();&lt;br /&gt;
        return compact&lt;br /&gt;
            ? makeGuideGeometry(d.COMPACT_HEIGHT, d.COMPACT_TOP_FRAME, d.COMPACT_BOTTOM_FRAME, 1)&lt;br /&gt;
            : makeGuideGeometry(d.HEIGHT, d.TOP_FRAME, d.BOTTOM_FRAME, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function interpolateGuideGeometry(from, to, progress) {&lt;br /&gt;
        function between(start, end) {&lt;br /&gt;
            return start + (end - start) * progress;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return makeGuideGeometry(&lt;br /&gt;
            between(from.height, to.height),&lt;br /&gt;
            between(from.topFrame, to.topFrame),&lt;br /&gt;
            between(from.bottomFrame, to.bottomFrame),&lt;br /&gt;
            between(from.sideFrameProgress, to.sideFrameProgress)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyGuideRowHeight(mount, nav, svg, geometry) {&lt;br /&gt;
        var height = geometry.rowHeight + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        [mount, nav, svg].forEach(function (node) {&lt;br /&gt;
            if (!node || !node.style) return;&lt;br /&gt;
            node.style.setProperty(&#039;height&#039;, height, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;min-height&#039;, height, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;max-height&#039;, height, &#039;important&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestGuideFrame(callback) {&lt;br /&gt;
        if (typeof window.requestAnimationFrame === &#039;function&#039;) {&lt;br /&gt;
            return window.requestAnimationFrame(callback);&lt;br /&gt;
        }&lt;br /&gt;
        return window.setTimeout(function () { callback(Date.now()); }, 16);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function cancelGuideFrame(handle) {&lt;br /&gt;
        if (!handle) return;&lt;br /&gt;
        if (typeof window.cancelAnimationFrame === &#039;function&#039;) {&lt;br /&gt;
            window.cancelAnimationFrame(handle);&lt;br /&gt;
        } else {&lt;br /&gt;
            window.clearTimeout(handle);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function animateGuideLayout(portal, compact) {&lt;br /&gt;
        var from = portal &amp;amp;&amp;amp; portal.GuideNavGeometry&lt;br /&gt;
            ? portal.GuideNavGeometry&lt;br /&gt;
            : getGuideGeometry(!compact);&lt;br /&gt;
        var to = getGuideGeometry(compact);&lt;br /&gt;
        var startedAt = null;&lt;br /&gt;
        var duration = 340;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
        if (guideLayoutAnimationFrame) {&lt;br /&gt;
            cancelGuideFrame(guideLayoutAnimationFrame);&lt;br /&gt;
            guideLayoutAnimationFrame = 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (window.matchMedia &amp;amp;&amp;amp; window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            portal.GuideNavGeometry = to;&lt;br /&gt;
            render(portal, to);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function step(now) {&lt;br /&gt;
            var elapsed;&lt;br /&gt;
            var progress;&lt;br /&gt;
            var eased;&lt;br /&gt;
&lt;br /&gt;
            if (startedAt === null) startedAt = now;&lt;br /&gt;
            elapsed = Math.min(1, (now - startedAt) / duration);&lt;br /&gt;
            eased = 1 - Math.pow(1 - elapsed, 3);&lt;br /&gt;
            progress = interpolateGuideGeometry(from, to, eased);&lt;br /&gt;
            portal.GuideNavGeometry = progress;&lt;br /&gt;
            render(portal, progress);&lt;br /&gt;
&lt;br /&gt;
            if (elapsed &amp;lt; 1) {&lt;br /&gt;
                guideLayoutAnimationFrame = requestGuideFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.GuideNavGeometry = to;&lt;br /&gt;
                guideLayoutAnimationFrame = 0;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        guideLayoutAnimationFrame = requestGuideFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildGuideButton(geometry) {&lt;br /&gt;
        var seams = geometryApi().seams;&lt;br /&gt;
        var leftTop;&lt;br /&gt;
        var rightTop;&lt;br /&gt;
        var leftDir;&lt;br /&gt;
        var rightDir;&lt;br /&gt;
&lt;br /&gt;
        if (!seams || !seams.width) return null;&lt;br /&gt;
        leftTop = geometryApi().slantX(&lt;br /&gt;
            seams.settingsLeftTopX,&lt;br /&gt;
            seams.buttonBottom,&lt;br /&gt;
            seams.settingsLeftDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        rightTop = geometryApi().slantX(&lt;br /&gt;
            seams.corpRightTopX,&lt;br /&gt;
            seams.buttonBottom,&lt;br /&gt;
            seams.corpRightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        leftDir = -seams.settingsLeftDir;&lt;br /&gt;
        rightDir = -seams.corpRightDir;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            leftTopX:leftTop,&lt;br /&gt;
            rightTopX:rightTop,&lt;br /&gt;
            leftDir:leftDir,&lt;br /&gt;
            rightDir:rightDir,&lt;br /&gt;
            points:shiftY(&lt;br /&gt;
                geometryApi().segment(leftTop, rightTop, geometry.height, leftDir, rightDir, 0),&lt;br /&gt;
                geometry.buttonTop&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function guideLayer(button, geometry, amount) {&lt;br /&gt;
        return shiftY(&lt;br /&gt;
            geometryApi().segment(&lt;br /&gt;
                button.leftTopX + amount,&lt;br /&gt;
                button.rightTopX - amount,&lt;br /&gt;
                geometry.height - amount * 2,&lt;br /&gt;
                button.leftDir,&lt;br /&gt;
                button.rightDir,&lt;br /&gt;
                amount % geometryApi().FRAME.SLANT_RISE&lt;br /&gt;
            ),&lt;br /&gt;
            geometry.buttonTop + amount&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendContinuousBottomFrame(svg, button, width, geometry) {&lt;br /&gt;
        var frame = geometryApi().FRAME;&lt;br /&gt;
        var group = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var leftTarget = button.points[0][0] - frame.FRAME_FACE - frame.GAP;&lt;br /&gt;
        var rightTarget = button.points[1][0] + frame.FRAME_FACE + frame.GAP;&lt;br /&gt;
        var leftBase = leftTarget - geometryApi().slantX(0, geometry.buttonTop, button.leftDir, 0);&lt;br /&gt;
        var rightBase = rightTarget - geometryApi().slantX(0, geometry.buttonTop, button.rightDir, 0);&lt;br /&gt;
        var outer = geometryApi().segment(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.rowHeight,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        var well = geometryApi().segmentAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.topFrame,&lt;br /&gt;
            geometry.rowHeight - geometry.bottomFrame,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.FRAME_FACE,&lt;br /&gt;
            frame.FRAME_FACE&lt;br /&gt;
        );&lt;br /&gt;
        var bevel = geometryApi().segmentAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            frame.BORDER + frame.BEVEL,&lt;br /&gt;
            geometry.rowHeight - frame.BORDER - frame.BEVEL,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER + frame.BEVEL,&lt;br /&gt;
            frame.BORDER + frame.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        group.setAttribute(&#039;class&#039;, &#039;portal-guide-arch-frame&#039;);&lt;br /&gt;
        group.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-guide-arch-outer&#039;, outer));&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-guide-arch-well&#039;, well));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[3], outer[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[0], outer[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[1], outer[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-highlight&#039;, bevel[0], bevel[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-highlight&#039;, bevel[1], bevel[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-shadow&#039;, bevel[3], bevel[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-highlight&#039;, well[0], well[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-highlight&#039;, well[1], well[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-shadow&#039;, well[3], well[0]));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyPanel(portal) {&lt;br /&gt;
        var panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        var manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) {&lt;br /&gt;
                    topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                } else {&lt;br /&gt;
                    topMount.parentNode.appendChild(panel);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) {&lt;br /&gt;
            well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            panel: panel,&lt;br /&gt;
            well: well&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBodyScreenHidden(screen, hidden) {&lt;br /&gt;
        if (!screen) return;&lt;br /&gt;
&lt;br /&gt;
        screen.classList.toggle(&#039;is-active&#039;, !hidden);&lt;br /&gt;
        screen.hidden = hidden;&lt;br /&gt;
        screen.setAttribute(&#039;aria-hidden&#039;, hidden ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateGuideButtonState(portal, active) {&lt;br /&gt;
        var guideActive = active === &#039;guide&#039;;&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var target = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-anchor&#039;), function (anchor) {&lt;br /&gt;
            anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
            anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            anchor.setAttribute(&#039;data-main-body-target&#039;, target);&lt;br /&gt;
            anchor.removeAttribute(&#039;aria-current&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-label&#039;), function (text) {&lt;br /&gt;
            text.textContent = label;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-nav&#039;), function (nav) {&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureGuideDummyMenu(guide) {&lt;br /&gt;
        var menu;&lt;br /&gt;
&lt;br /&gt;
        if (!guide) return null;&lt;br /&gt;
&lt;br /&gt;
        menu = guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
        if (menu) return menu;&lt;br /&gt;
&lt;br /&gt;
        menu = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        menu.className = &#039;main-guide-menu&#039;;&lt;br /&gt;
        menu.setAttribute(&#039;data-guide-dummy-menu&#039;, &#039;1&#039;);&lt;br /&gt;
        menu.setAttribute(&#039;aria-label&#039;, &#039;길라잡이 항목 예시&#039;);&lt;br /&gt;
&lt;br /&gt;
        GUIDE_DUMMY_ITEMS.forEach(function (item, index) {&lt;br /&gt;
            var wrapper = document.createElement(&#039;div&#039;);&lt;br /&gt;
            var button = document.createElement(&#039;button&#039;);&lt;br /&gt;
            var number = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var arrow = document.createElement(&#039;span&#039;);&lt;br /&gt;
&lt;br /&gt;
            wrapper.className = &#039;main-guide-menu-item&#039;;&lt;br /&gt;
            wrapper.style.setProperty(&#039;--guide-menu-order&#039;, String(index));&lt;br /&gt;
&lt;br /&gt;
            button.type = &#039;button&#039;;&lt;br /&gt;
            button.className = &#039;main-guide-menu-button&#039;;&lt;br /&gt;
            button.setAttribute(&#039;data-guide-dummy-button&#039;, item.key);&lt;br /&gt;
            button.setAttribute(&#039;aria-label&#039;, item.label + &#039; (더미 버튼)&#039;);&lt;br /&gt;
&lt;br /&gt;
            number.className = &#039;main-guide-menu-number&#039;;&lt;br /&gt;
            number.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            number.textContent = String(index + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
            label.className = &#039;main-guide-menu-label&#039;;&lt;br /&gt;
            label.textContent = item.label;&lt;br /&gt;
&lt;br /&gt;
            arrow.className = &#039;main-guide-menu-arrow&#039;;&lt;br /&gt;
            arrow.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            arrow.textContent = &#039;›&#039;;&lt;br /&gt;
&lt;br /&gt;
            button.appendChild(number);&lt;br /&gt;
            button.appendChild(label);&lt;br /&gt;
            button.appendChild(arrow);&lt;br /&gt;
            button.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
            });&lt;br /&gt;
            wrapper.appendChild(button);&lt;br /&gt;
            menu.appendChild(wrapper);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        guide.insertBefore(menu, guide.firstChild);&lt;br /&gt;
        return menu;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function restartGuideMenuAnimation(guide) {&lt;br /&gt;
        if (!guide || !guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        /* 같은 화면에 재진입해도 첫 프레임부터 순차 진입을 다시 시작한다. */&lt;br /&gt;
        void guide.offsetWidth;&lt;br /&gt;
        guide.classList.add(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyScreens(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var manifesto = layout.well.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
        var guide = layout.well.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;);&lt;br /&gt;
        var panels;&lt;br /&gt;
        var active = layout.well.getAttribute(&#039;data-active-main-body-screen&#039;) ||&lt;br /&gt;
            portal.getAttribute(&#039;data-main-body-screen&#039;) ||&lt;br /&gt;
            &#039;manifesto&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (manifesto) {&lt;br /&gt;
            manifesto.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            manifesto.setAttribute(&#039;data-main-body-screen&#039;, &#039;manifesto&#039;);&lt;br /&gt;
            if (!manifesto.id) manifesto.id = &#039;main-manifesto-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!guide) {&lt;br /&gt;
            guide = document.createElement(&#039;section&#039;);&lt;br /&gt;
            guide.className = &#039;main-body-screen main-guide-screen&#039;;&lt;br /&gt;
            guide.setAttribute(&#039;data-main-body-screen&#039;, &#039;guide&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;data-main-guide-screen-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;aria-label&#039;, &#039;길라잡이&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;role&#039;, &#039;region&#039;);&lt;br /&gt;
            guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
            layout.well.appendChild(guide);&lt;br /&gt;
        } else {&lt;br /&gt;
            guide.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            guide.classList.add(&#039;main-guide-screen&#039;);&lt;br /&gt;
            if (!guide.id) guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        ensureGuideDummyMenu(guide);&lt;br /&gt;
&lt;br /&gt;
        panels = Array.prototype.slice.call(&lt;br /&gt;
            layout.well.querySelectorAll(&#039;.main-body-screen[data-main-body-screen]&#039;)&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (active !== &#039;guide&#039;) active = &#039;manifesto&#039;;&lt;br /&gt;
        if (!panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === active;&lt;br /&gt;
        })) {&lt;br /&gt;
            active = &#039;manifesto&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(&lt;br /&gt;
                panel,&lt;br /&gt;
                panel.getAttribute(&#039;data-main-body-screen&#039;) !== active&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        layout.well.setAttribute(&#039;data-main-body-screen-controller-ready&#039;, &#039;1&#039;);&lt;br /&gt;
        layout.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; !guide.classList.contains(&#039;is-guide-menu-entering&#039;)) {&lt;br /&gt;
            restartGuideMenuAnimation(guide);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            well: layout.well,&lt;br /&gt;
            panels: panels,&lt;br /&gt;
            manifesto: manifesto,&lt;br /&gt;
            guide: guide,&lt;br /&gt;
            active: active&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBodyScreen(portal, screenName) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var previous;&lt;br /&gt;
        var active;&lt;br /&gt;
        var event;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        screenName = screenName === &#039;guide&#039; ? &#039;guide&#039; : &#039;manifesto&#039;;&lt;br /&gt;
        if (!screens.panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === screenName;&lt;br /&gt;
        })) return false;&lt;br /&gt;
&lt;br /&gt;
        previous = screens.active;&lt;br /&gt;
        active = screenName;&lt;br /&gt;
&lt;br /&gt;
        screens.panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(&lt;br /&gt;
                panel,&lt;br /&gt;
                panel.getAttribute(&#039;data-main-body-screen&#039;) !== active&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        screens.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; previous !== active) {&lt;br /&gt;
            restartGuideMenuAnimation(screens.guide);&lt;br /&gt;
        } else if (active !== &#039;guide&#039; &amp;amp;&amp;amp; screens.guide) {&lt;br /&gt;
            screens.guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            animateGuideLayout(portal, active === &#039;guide&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            try {&lt;br /&gt;
                event = new CustomEvent(&#039;mainbodyscreenchange&#039;, {&lt;br /&gt;
                    bubbles: true,&lt;br /&gt;
                    detail: { screen: active, previous: previous }&lt;br /&gt;
                });&lt;br /&gt;
                portal.dispatchEvent(event);&lt;br /&gt;
            } catch (error) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function toggleBodyScreen(portal) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        target = screens.active === &#039;guide&#039; ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        return activateBodyScreen(portal, target);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleGuideScreenCapture(event) {&lt;br /&gt;
        var target;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var portal;&lt;br /&gt;
&lt;br /&gt;
        if (!event || event.__mainGuideScreenHandled) return;&lt;br /&gt;
        if (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        target = event.target;&lt;br /&gt;
        anchor = target &amp;amp;&amp;amp; target.closest&lt;br /&gt;
            ? target.closest(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)&lt;br /&gt;
            : null;&lt;br /&gt;
        if (!anchor) return;&lt;br /&gt;
&lt;br /&gt;
        portal = anchor.closest ? anchor.closest(&#039;.main-portal&#039;) : null;&lt;br /&gt;
        if (!portal || !toggleBodyScreen(portal)) return;&lt;br /&gt;
&lt;br /&gt;
        event.__mainGuideScreenHandled = true;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) {&lt;br /&gt;
            event.stopImmediatePropagation();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    이전 실행본의 ResizeObserver가 오래된 문서 링크를 다시 그려도&lt;br /&gt;
    전역 SPA 링크 처리보다 먼저 화면 전환으로 고정한다.&lt;br /&gt;
    */&lt;br /&gt;
    if (window.BottomGuideNavScreenCaptureHandler) {&lt;br /&gt;
        document.removeEventListener(&#039;click&#039;, window.BottomGuideNavScreenCaptureHandler, true);&lt;br /&gt;
    }&lt;br /&gt;
    window.BottomGuideNavScreenCaptureHandler = handleGuideScreenCapture;&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleGuideScreenCapture, true);&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        ensureBodyScreens(portal);&lt;br /&gt;
&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
            mount.setAttribute(&#039;data-bottom-guide-generated&#039;, &#039;1&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        길라잡이는 우물 안의 독립 버튼이 아니라&lt;br /&gt;
        main-body-panel 하단 프레임에 융합된 돌출부다.&lt;br /&gt;
        */&lt;br /&gt;
        if (mount.parentNode !== layout.panel) {&lt;br /&gt;
            layout.panel.appendChild(mount);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(portal, suppliedGeometry) {&lt;br /&gt;
        /*&lt;br /&gt;
         * FrameGeometry(window.CLBI.frame)가 아직 로드되지 않았으면 렌더를 미룬다.&lt;br /&gt;
         * 이 파일이 FrameGeometry.js보다 먼저 실행될 수 있으므로, 준비되지 않은&lt;br /&gt;
         * 상태에서 geometryApi().readWidth(...) 등을 호출하면 undefined 참조로 죽는다.&lt;br /&gt;
         * 준비될 때까지 짧게 재시도한다.&lt;br /&gt;
         */&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        if (!api || !api.FRAME || typeof api.readWidth !== &#039;function&#039;) {&lt;br /&gt;
            window.clearTimeout(geometryWaitTimer);&lt;br /&gt;
            geometryWaitTimer = window.setTimeout(function () {&lt;br /&gt;
                geometryWaitTimer = 0;&lt;br /&gt;
                render(portal, suppliedGeometry);&lt;br /&gt;
            }, 50);&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var mount = ensureMount(portal);&lt;br /&gt;
        var width = api.readWidth(mount);&lt;br /&gt;
        var guideActive = portal.getAttribute(&#039;data-main-body-screen&#039;) === &#039;guide&#039;;&lt;br /&gt;
        var geometry = suppliedGeometry || portal.GuideNavGeometry || getGuideGeometry(guideActive);&lt;br /&gt;
&lt;br /&gt;
        if (!width) return false;&lt;br /&gt;
&lt;br /&gt;
        portal.GuideNavGeometry = geometry;&lt;br /&gt;
&lt;br /&gt;
        var button = buildGuideButton(geometry);&lt;br /&gt;
        var points;&lt;br /&gt;
        var surfacePoints;&lt;br /&gt;
        var insetPoints;&lt;br /&gt;
        var center;&lt;br /&gt;
&lt;br /&gt;
        if (!button) return false;&lt;br /&gt;
        points = button.points;&lt;br /&gt;
        surfacePoints = guideLayer(button, geometry, api.FRAME.BORDER);&lt;br /&gt;
        insetPoints = guideLayer(&lt;br /&gt;
            button,&lt;br /&gt;
            geometry,&lt;br /&gt;
            api.FRAME.BORDER + api.FRAME.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
        center = centerOfPoints(surfacePoints);&lt;br /&gt;
        var nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        var svg = createSvgElement(&#039;svg&#039;);&lt;br /&gt;
        var anchor = createSvgElement(&#039;a&#039;);&lt;br /&gt;
        var buttonGeometry = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var targetScreen = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
        var text = createSvgElement(&#039;text&#039;);&lt;br /&gt;
        var outer = createPolygon(&#039;portal-cat-edge&#039;, points);&lt;br /&gt;
        var surface = createPolygon(&#039;portal-cat-surface&#039;, surfacePoints);&lt;br /&gt;
        var hit = createPolygon(&#039;portal-cat-hit&#039;, points);&lt;br /&gt;
&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-guide-nav is-continuous-weld&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
&lt;br /&gt;
        svg.setAttribute(&#039;class&#039;, &#039;portal-category-svg portal-guide-svg&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + width + &#039; &#039; + geometry.rowHeight);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, geometry.rowHeight);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        appendContinuousBottomFrame(svg, button, width, geometry);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;class&#039;, &#039;portal-cat-anchor portal-guide-anchor&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        anchor.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, &#039;guide&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-main-body-target&#039;, targetScreen);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-controls&#039;, &#039;main-manifesto-screen main-guide-screen&#039;);&lt;br /&gt;
        buttonGeometry.setAttribute(&#039;class&#039;, &#039;portal-guide-button-geometry&#039;);&lt;br /&gt;
&lt;br /&gt;
        anchor.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            if (&lt;br /&gt;
                event &amp;amp;&amp;amp;&lt;br /&gt;
                (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (toggleBodyScreen(portal)) {&lt;br /&gt;
                if (event) event.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        outer.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        surface.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;class&#039;, &#039;portal-cat-label portal-guide-label&#039;);&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, Math.round(center[0]));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, Math.round((geometry.buttonTop + geometry.buttonBottom) / 2) + 1);&lt;br /&gt;
        text.setAttribute(&#039;font-size&#039;, &#039;12&#039;);&lt;br /&gt;
        text.setAttribute(&#039;font-weight&#039;, &#039;700&#039;);&lt;br /&gt;
        text.setAttribute(&#039;text-anchor&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;dominant-baseline&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        text.textContent = label;&lt;br /&gt;
&lt;br /&gt;
        buttonGeometry.appendChild(outer);&lt;br /&gt;
        buttonGeometry.appendChild(surface);&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-top&#039;,&lt;br /&gt;
            insetPoints[0],&lt;br /&gt;
            insetPoints[1]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-right&#039;,&lt;br /&gt;
            insetPoints[1],&lt;br /&gt;
            insetPoints[2]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-bottom&#039;,&lt;br /&gt;
            insetPoints[2],&lt;br /&gt;
            insetPoints[3]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-left&#039;,&lt;br /&gt;
            insetPoints[3],&lt;br /&gt;
            insetPoints[0]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(hit);&lt;br /&gt;
        anchor.appendChild(buttonGeometry);&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        svg.appendChild(anchor);&lt;br /&gt;
        nav.appendChild(svg);&lt;br /&gt;
&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        applyGuideRowHeight(mount, nav, svg, geometry);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-width&#039;, String(width));&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findPortal(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, attempt) {&lt;br /&gt;
        window.clearTimeout(retryTimer);&lt;br /&gt;
&lt;br /&gt;
        retryTimer = window.setTimeout(function () {&lt;br /&gt;
            var portal = findPortal(root);&lt;br /&gt;
&lt;br /&gt;
            if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
            if (!render(portal) &amp;amp;&amp;amp; attempt &amp;lt; 20) {&lt;br /&gt;
                schedule(root, attempt + 1);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (observer) observer.disconnect();&lt;br /&gt;
&lt;br /&gt;
            if (window.ResizeObserver) {&lt;br /&gt;
                observer = new ResizeObserver(function () {&lt;br /&gt;
                    window.clearTimeout(resizeTimer);&lt;br /&gt;
                    resizeTimer = window.setTimeout(function () {&lt;br /&gt;
                        render(portal);&lt;br /&gt;
                    }, 60);&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                observer.observe(portal);&lt;br /&gt;
                var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
                var guideMount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
                if (topMount) observer.observe(topMount);&lt;br /&gt;
                if (guideMount) observer.observe(guideMount);&lt;br /&gt;
            }&lt;br /&gt;
        }, attempt ? 60 : 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot(root) {&lt;br /&gt;
        var scope = root || document;&lt;br /&gt;
        schedule(scope, 0);&lt;br /&gt;
        loadManifestoIntro(scope);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {&lt;br /&gt;
            boot(document);&lt;br /&gt;
        }, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* 일반 링크 진입과 새로고침의 최종 로드 시점도 명시적으로 보장한다. */&lt;br /&gt;
    window.addEventListener(&#039;load&#039;, function () {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }, { once: true });&lt;br /&gt;
&lt;br /&gt;
    /* bfcache 복원과 브라우저 뒤로/앞으로 이동에서도 새 DOM만 초기화한다. */&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, function () {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            boot($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BottomGuideNav = {&lt;br /&gt;
        version: &#039;20260717-shared-frame-reverse-slant-001&#039;,&lt;br /&gt;
        render: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? render(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        activate: function (screenName) {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? activateBodyScreen(portal, screenName) : false;&lt;br /&gt;
        },&lt;br /&gt;
        toggle: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? toggleBodyScreen(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        status: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            var mount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
            return {&lt;br /&gt;
                loaded: true,&lt;br /&gt;
                portal: !!portal,&lt;br /&gt;
                mount: !!mount,&lt;br /&gt;
                rendered: !!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-svg&#039;)),&lt;br /&gt;
                generated: !!(mount &amp;amp;&amp;amp; mount.getAttribute(&#039;data-bottom-guide-generated&#039;)),&lt;br /&gt;
                panel: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;)),&lt;br /&gt;
                well: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-well&#039;)),&lt;br /&gt;
                insidePanel: !!(mount &amp;amp;&amp;amp; mount.parentNode &amp;amp;&amp;amp; mount.parentNode.classList.contains(&#039;main-body-panel&#039;)),&lt;br /&gt;
                welded: !!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-nav.is-continuous-weld&#039;)),&lt;br /&gt;
                activeScreen: portal ? portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039; : &#039;&#039;,&lt;br /&gt;
                guideScreen: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;)),&lt;br /&gt;
                width: mount ? mount.getAttribute(&#039;data-bottom-guide-width&#039;) || &#039;&#039; : &#039;&#039;&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4671</id>
		<title>미디어위키:BottomGuideNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4671"/>
		<updated>2026-07-17T02:52:50Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
BottomGuideNav&lt;br /&gt;
대문 하단 「길라잡이」 SVG 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
상단 CategoryNav와 분리해 로드한다.&lt;br /&gt;
FrameGeometry.js가 공유한 설정–프로젝트–기업 및 공동체 seam을 사용하며,&lt;br /&gt;
상단 SVG나 화면 rect를 다시 읽지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var COMPONENT = &#039;category-guide-nav&#039;;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * FrameGeometry(window.CLBI.frame) 의존은 반드시 렌더 시점에 지연 접근한다.&lt;br /&gt;
     * 모듈 로드 순서상 이 파일이 FrameGeometry.js보다 먼저 실행될 수 있으므로,&lt;br /&gt;
     * 최상단에서 FRAME_API.FRAME 을 즉시 읽으면 undefined 참조로 파일 전체가 죽는다.&lt;br /&gt;
     * (다른 프레임 모듈들과 동일하게 geometryApi() 함수를 통해 접근한다.)&lt;br /&gt;
     */&lt;br /&gt;
    function geometryApi() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* 세로 규격은 매 렌더 시 FrameGeometry 상수에서 새로 파생한다. */&lt;br /&gt;
    function guideDims() {&lt;br /&gt;
        var f = geometryApi().FRAME;&lt;br /&gt;
        var top = f.FRAME_FACE;&lt;br /&gt;
        var bottom = f.FRAME_FACE;&lt;br /&gt;
        var height = f.BUTTON_FACE;&lt;br /&gt;
        var gap = f.GAP;&lt;br /&gt;
        return {&lt;br /&gt;
            HEIGHT: height,&lt;br /&gt;
            TOP_FRAME: top,&lt;br /&gt;
            BOTTOM_FRAME: bottom,&lt;br /&gt;
            GAP: gap,&lt;br /&gt;
            ROW_HEIGHT: top + gap + height + gap + bottom,&lt;br /&gt;
            BUTTON_TOP: top + gap,&lt;br /&gt;
            BUTTON_BOTTOM: top + gap + height,&lt;br /&gt;
            COMPACT_HEIGHT: height,&lt;br /&gt;
            COMPACT_TOP_FRAME: top,&lt;br /&gt;
            COMPACT_BOTTOM_FRAME: bottom&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
    var retryTimer = 0;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var guideLayoutAnimationFrame = 0;&lt;br /&gt;
    var observer = null;&lt;br /&gt;
    var manifestoIntroPromise = null;&lt;br /&gt;
    var MANIFESTO_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var MANIFESTO_INTRO_VERSION = &#039;20260713-divider-equal-gap-005&#039;;&lt;br /&gt;
    var GUIDE_DUMMY_ITEMS = [&lt;br /&gt;
        { key: &#039;start&#039;, label: &#039;처음 오셨다면&#039; },&lt;br /&gt;
        { key: &#039;world&#039;, label: &#039;세계관&#039; },&lt;br /&gt;
        { key: &#039;era&#039;, label: &#039;시대&#039; },&lt;br /&gt;
        { key: &#039;people&#039;, label: &#039;인물&#039; },&lt;br /&gt;
        { key: &#039;anecdote&#039;, label: &#039;에넥도트&#039; },&lt;br /&gt;
        { key: &#039;participate&#039;, label: &#039;참여 안내&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function revealManifesto(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        var portal = scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;) || document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var copy;&lt;br /&gt;
&lt;br /&gt;
        /* 이전 캐시에서 남은 전역 잠금을 즉시 해제한다. */&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-pending&#039;);&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-active&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-prepared&#039;);&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-running&#039;);&lt;br /&gt;
        title = portal.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        copy = portal.querySelector(&#039;.main-manifesto-copy&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (title) {&lt;br /&gt;
            title.style.opacity = &#039;1&#039;;&lt;br /&gt;
            title.style.borderBottom = &#039;0&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (copy) copy.style.visibility = &#039;visible&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line&#039;), function (line) {&lt;br /&gt;
            line.style.height = &#039;26px&#039;;&lt;br /&gt;
            line.style.maxHeight = &#039;26px&#039;;&lt;br /&gt;
            line.style.opacity = &#039;1&#039;;&lt;br /&gt;
            line.style.overflow = &#039;visible&#039;;&lt;br /&gt;
            line.style.clipPath = &#039;inset(0 0 0 0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line-inner&#039;), function (inner) {&lt;br /&gt;
            inner.style.opacity = &#039;1&#039;;&lt;br /&gt;
            inner.style.transform = &#039;translateY(0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifestoIntro(root) {&lt;br /&gt;
        if (&lt;br /&gt;
            window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
            window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
            typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(window.ManifestoIntro.init(root || document)).catch(function () {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    return false;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                revealManifesto(root);&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifestoIntroPromise) return manifestoIntroPromise;&lt;br /&gt;
&lt;br /&gt;
        manifestoIntroPromise = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                if (!ok) {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    manifestoIntroPromise = null;&lt;br /&gt;
                }&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            script.src = &#039;/index.php?title=&#039; + encodeURIComponent(&#039;MediaWiki:ManifestoIntro.js&#039;) +&lt;br /&gt;
                &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&amp;amp;v=&#039; + encodeURIComponent(MANIFESTO_INTRO_VERSION) +&lt;br /&gt;
                &#039;&amp;amp;_manifestoFresh=&#039; + encodeURIComponent(String(Date.now()));&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-manifesto-intro-script&#039;, &#039;1&#039;);&lt;br /&gt;
            script.onload = function () {&lt;br /&gt;
                if (&lt;br /&gt;
                    window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
                    window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
                    typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    Promise.resolve(window.ManifestoIntro.init(root || document))&lt;br /&gt;
                        .then(function () { settle(true); })&lt;br /&gt;
                        .catch(function () { settle(false); });&lt;br /&gt;
                } else {&lt;br /&gt;
                    settle(false);&lt;br /&gt;
                }&lt;br /&gt;
            };&lt;br /&gt;
            script.onerror = function () {&lt;br /&gt;
                settle(false);&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, MANIFESTO_LOAD_TIMEOUT_MS);&lt;br /&gt;
&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return manifestoIntroPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    revealManifesto(document);&lt;br /&gt;
&lt;br /&gt;
    function createSvgElement(name) {&lt;br /&gt;
        return geometryApi().create(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildWikiPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) {&lt;br /&gt;
            return window.buildWikiPath(title);&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPolygon(className, points) {&lt;br /&gt;
        return geometryApi().polygon(className, points);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createLine(className, start, end) {&lt;br /&gt;
        return geometryApi().line(className, start, end);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createRect(className, x, y, width, height) {&lt;br /&gt;
        return geometryApi().rect(className, x, y, width, height);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function centerOfPoints(points) {&lt;br /&gt;
        return [geometryApi().centerX(points), Math.round((points[0][1] + points[2][1]) / 2)];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function shiftY(points, amount) {&lt;br /&gt;
        return points.map(function (point) {&lt;br /&gt;
            return [Math.round(point[0]), Math.round(point[1] + amount)];&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeGuideGeometry(height, topFrame, bottomFrame, sideFrameProgress) {&lt;br /&gt;
        var gap = geometryApi().FRAME.GAP;&lt;br /&gt;
        height = Math.round(height);&lt;br /&gt;
        topFrame = Math.round(topFrame);&lt;br /&gt;
        bottomFrame = Math.round(bottomFrame);&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            height: height,&lt;br /&gt;
            topFrame: topFrame,&lt;br /&gt;
            bottomFrame: bottomFrame,&lt;br /&gt;
            buttonTop: topFrame + gap,&lt;br /&gt;
            buttonBottom: topFrame + gap + height,&lt;br /&gt;
            rowHeight: topFrame + gap + height + gap + bottomFrame,&lt;br /&gt;
            sideFrameProgress: Math.max(0, Math.min(1, Number(sideFrameProgress || 0)))&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getGuideGeometry(compact) {&lt;br /&gt;
        var d = guideDims();&lt;br /&gt;
        return compact&lt;br /&gt;
            ? makeGuideGeometry(d.COMPACT_HEIGHT, d.COMPACT_TOP_FRAME, d.COMPACT_BOTTOM_FRAME, 1)&lt;br /&gt;
            : makeGuideGeometry(d.HEIGHT, d.TOP_FRAME, d.BOTTOM_FRAME, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function interpolateGuideGeometry(from, to, progress) {&lt;br /&gt;
        function between(start, end) {&lt;br /&gt;
            return start + (end - start) * progress;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return makeGuideGeometry(&lt;br /&gt;
            between(from.height, to.height),&lt;br /&gt;
            between(from.topFrame, to.topFrame),&lt;br /&gt;
            between(from.bottomFrame, to.bottomFrame),&lt;br /&gt;
            between(from.sideFrameProgress, to.sideFrameProgress)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyGuideRowHeight(mount, nav, svg, geometry) {&lt;br /&gt;
        var height = geometry.rowHeight + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        [mount, nav, svg].forEach(function (node) {&lt;br /&gt;
            if (!node || !node.style) return;&lt;br /&gt;
            node.style.setProperty(&#039;height&#039;, height, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;min-height&#039;, height, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;max-height&#039;, height, &#039;important&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestGuideFrame(callback) {&lt;br /&gt;
        if (typeof window.requestAnimationFrame === &#039;function&#039;) {&lt;br /&gt;
            return window.requestAnimationFrame(callback);&lt;br /&gt;
        }&lt;br /&gt;
        return window.setTimeout(function () { callback(Date.now()); }, 16);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function cancelGuideFrame(handle) {&lt;br /&gt;
        if (!handle) return;&lt;br /&gt;
        if (typeof window.cancelAnimationFrame === &#039;function&#039;) {&lt;br /&gt;
            window.cancelAnimationFrame(handle);&lt;br /&gt;
        } else {&lt;br /&gt;
            window.clearTimeout(handle);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function animateGuideLayout(portal, compact) {&lt;br /&gt;
        var from = portal &amp;amp;&amp;amp; portal.GuideNavGeometry&lt;br /&gt;
            ? portal.GuideNavGeometry&lt;br /&gt;
            : getGuideGeometry(!compact);&lt;br /&gt;
        var to = getGuideGeometry(compact);&lt;br /&gt;
        var startedAt = null;&lt;br /&gt;
        var duration = 340;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
        if (guideLayoutAnimationFrame) {&lt;br /&gt;
            cancelGuideFrame(guideLayoutAnimationFrame);&lt;br /&gt;
            guideLayoutAnimationFrame = 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (window.matchMedia &amp;amp;&amp;amp; window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            portal.GuideNavGeometry = to;&lt;br /&gt;
            render(portal, to);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function step(now) {&lt;br /&gt;
            var elapsed;&lt;br /&gt;
            var progress;&lt;br /&gt;
            var eased;&lt;br /&gt;
&lt;br /&gt;
            if (startedAt === null) startedAt = now;&lt;br /&gt;
            elapsed = Math.min(1, (now - startedAt) / duration);&lt;br /&gt;
            eased = 1 - Math.pow(1 - elapsed, 3);&lt;br /&gt;
            progress = interpolateGuideGeometry(from, to, eased);&lt;br /&gt;
            portal.GuideNavGeometry = progress;&lt;br /&gt;
            render(portal, progress);&lt;br /&gt;
&lt;br /&gt;
            if (elapsed &amp;lt; 1) {&lt;br /&gt;
                guideLayoutAnimationFrame = requestGuideFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.GuideNavGeometry = to;&lt;br /&gt;
                guideLayoutAnimationFrame = 0;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        guideLayoutAnimationFrame = requestGuideFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildGuideButton(geometry) {&lt;br /&gt;
        var seams = geometryApi().seams;&lt;br /&gt;
        var leftTop;&lt;br /&gt;
        var rightTop;&lt;br /&gt;
        var leftDir;&lt;br /&gt;
        var rightDir;&lt;br /&gt;
&lt;br /&gt;
        if (!seams || !seams.width) return null;&lt;br /&gt;
        leftTop = geometryApi().slantX(&lt;br /&gt;
            seams.settingsLeftTopX,&lt;br /&gt;
            seams.buttonBottom,&lt;br /&gt;
            seams.settingsLeftDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        rightTop = geometryApi().slantX(&lt;br /&gt;
            seams.corpRightTopX,&lt;br /&gt;
            seams.buttonBottom,&lt;br /&gt;
            seams.corpRightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        leftDir = -seams.settingsLeftDir;&lt;br /&gt;
        rightDir = -seams.corpRightDir;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            leftTopX:leftTop,&lt;br /&gt;
            rightTopX:rightTop,&lt;br /&gt;
            leftDir:leftDir,&lt;br /&gt;
            rightDir:rightDir,&lt;br /&gt;
            points:shiftY(&lt;br /&gt;
                geometryApi().segment(leftTop, rightTop, geometry.height, leftDir, rightDir, 0),&lt;br /&gt;
                geometry.buttonTop&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function guideLayer(button, geometry, amount) {&lt;br /&gt;
        return shiftY(&lt;br /&gt;
            geometryApi().segment(&lt;br /&gt;
                button.leftTopX + amount,&lt;br /&gt;
                button.rightTopX - amount,&lt;br /&gt;
                geometry.height - amount * 2,&lt;br /&gt;
                button.leftDir,&lt;br /&gt;
                button.rightDir,&lt;br /&gt;
                amount % geometryApi().FRAME.SLANT_RISE&lt;br /&gt;
            ),&lt;br /&gt;
            geometry.buttonTop + amount&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendContinuousBottomFrame(svg, button, width, geometry) {&lt;br /&gt;
        var frame = geometryApi().FRAME;&lt;br /&gt;
        var group = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var leftTarget = button.points[0][0] - frame.FRAME_FACE - frame.GAP;&lt;br /&gt;
        var rightTarget = button.points[1][0] + frame.FRAME_FACE + frame.GAP;&lt;br /&gt;
        var leftBase = leftTarget - geometryApi().slantX(0, geometry.buttonTop, button.leftDir, 0);&lt;br /&gt;
        var rightBase = rightTarget - geometryApi().slantX(0, geometry.buttonTop, button.rightDir, 0);&lt;br /&gt;
        var outer = geometryApi().segment(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.rowHeight,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        var well = geometryApi().segmentAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.topFrame,&lt;br /&gt;
            geometry.rowHeight - geometry.bottomFrame,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.FRAME_FACE,&lt;br /&gt;
            frame.FRAME_FACE&lt;br /&gt;
        );&lt;br /&gt;
        var bevel = geometryApi().segmentAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            frame.BORDER + frame.BEVEL,&lt;br /&gt;
            geometry.rowHeight - frame.BORDER - frame.BEVEL,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER + frame.BEVEL,&lt;br /&gt;
            frame.BORDER + frame.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        group.setAttribute(&#039;class&#039;, &#039;portal-guide-arch-frame&#039;);&lt;br /&gt;
        group.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-guide-arch-outer&#039;, outer));&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-guide-arch-well&#039;, well));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[3], outer[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[0], outer[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[1], outer[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-highlight&#039;, bevel[0], bevel[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-highlight&#039;, bevel[1], bevel[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-shadow&#039;, bevel[3], bevel[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-highlight&#039;, well[0], well[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-highlight&#039;, well[1], well[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-shadow&#039;, well[3], well[0]));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyPanel(portal) {&lt;br /&gt;
        var panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        var manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) {&lt;br /&gt;
                    topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                } else {&lt;br /&gt;
                    topMount.parentNode.appendChild(panel);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) {&lt;br /&gt;
            well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            panel: panel,&lt;br /&gt;
            well: well&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBodyScreenHidden(screen, hidden) {&lt;br /&gt;
        if (!screen) return;&lt;br /&gt;
&lt;br /&gt;
        screen.classList.toggle(&#039;is-active&#039;, !hidden);&lt;br /&gt;
        screen.hidden = hidden;&lt;br /&gt;
        screen.setAttribute(&#039;aria-hidden&#039;, hidden ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateGuideButtonState(portal, active) {&lt;br /&gt;
        var guideActive = active === &#039;guide&#039;;&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var target = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-anchor&#039;), function (anchor) {&lt;br /&gt;
            anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
            anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            anchor.setAttribute(&#039;data-main-body-target&#039;, target);&lt;br /&gt;
            anchor.removeAttribute(&#039;aria-current&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-label&#039;), function (text) {&lt;br /&gt;
            text.textContent = label;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-nav&#039;), function (nav) {&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureGuideDummyMenu(guide) {&lt;br /&gt;
        var menu;&lt;br /&gt;
&lt;br /&gt;
        if (!guide) return null;&lt;br /&gt;
&lt;br /&gt;
        menu = guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
        if (menu) return menu;&lt;br /&gt;
&lt;br /&gt;
        menu = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        menu.className = &#039;main-guide-menu&#039;;&lt;br /&gt;
        menu.setAttribute(&#039;data-guide-dummy-menu&#039;, &#039;1&#039;);&lt;br /&gt;
        menu.setAttribute(&#039;aria-label&#039;, &#039;길라잡이 항목 예시&#039;);&lt;br /&gt;
&lt;br /&gt;
        GUIDE_DUMMY_ITEMS.forEach(function (item, index) {&lt;br /&gt;
            var wrapper = document.createElement(&#039;div&#039;);&lt;br /&gt;
            var button = document.createElement(&#039;button&#039;);&lt;br /&gt;
            var number = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var arrow = document.createElement(&#039;span&#039;);&lt;br /&gt;
&lt;br /&gt;
            wrapper.className = &#039;main-guide-menu-item&#039;;&lt;br /&gt;
            wrapper.style.setProperty(&#039;--guide-menu-order&#039;, String(index));&lt;br /&gt;
&lt;br /&gt;
            button.type = &#039;button&#039;;&lt;br /&gt;
            button.className = &#039;main-guide-menu-button&#039;;&lt;br /&gt;
            button.setAttribute(&#039;data-guide-dummy-button&#039;, item.key);&lt;br /&gt;
            button.setAttribute(&#039;aria-label&#039;, item.label + &#039; (더미 버튼)&#039;);&lt;br /&gt;
&lt;br /&gt;
            number.className = &#039;main-guide-menu-number&#039;;&lt;br /&gt;
            number.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            number.textContent = String(index + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
            label.className = &#039;main-guide-menu-label&#039;;&lt;br /&gt;
            label.textContent = item.label;&lt;br /&gt;
&lt;br /&gt;
            arrow.className = &#039;main-guide-menu-arrow&#039;;&lt;br /&gt;
            arrow.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            arrow.textContent = &#039;›&#039;;&lt;br /&gt;
&lt;br /&gt;
            button.appendChild(number);&lt;br /&gt;
            button.appendChild(label);&lt;br /&gt;
            button.appendChild(arrow);&lt;br /&gt;
            button.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
            });&lt;br /&gt;
            wrapper.appendChild(button);&lt;br /&gt;
            menu.appendChild(wrapper);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        guide.insertBefore(menu, guide.firstChild);&lt;br /&gt;
        return menu;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function restartGuideMenuAnimation(guide) {&lt;br /&gt;
        if (!guide || !guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        /* 같은 화면에 재진입해도 첫 프레임부터 순차 진입을 다시 시작한다. */&lt;br /&gt;
        void guide.offsetWidth;&lt;br /&gt;
        guide.classList.add(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyScreens(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var manifesto = layout.well.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
        var guide = layout.well.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;);&lt;br /&gt;
        var panels;&lt;br /&gt;
        var active = layout.well.getAttribute(&#039;data-active-main-body-screen&#039;) ||&lt;br /&gt;
            portal.getAttribute(&#039;data-main-body-screen&#039;) ||&lt;br /&gt;
            &#039;manifesto&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (manifesto) {&lt;br /&gt;
            manifesto.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            manifesto.setAttribute(&#039;data-main-body-screen&#039;, &#039;manifesto&#039;);&lt;br /&gt;
            if (!manifesto.id) manifesto.id = &#039;main-manifesto-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!guide) {&lt;br /&gt;
            guide = document.createElement(&#039;section&#039;);&lt;br /&gt;
            guide.className = &#039;main-body-screen main-guide-screen&#039;;&lt;br /&gt;
            guide.setAttribute(&#039;data-main-body-screen&#039;, &#039;guide&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;data-main-guide-screen-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;aria-label&#039;, &#039;길라잡이&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;role&#039;, &#039;region&#039;);&lt;br /&gt;
            guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
            layout.well.appendChild(guide);&lt;br /&gt;
        } else {&lt;br /&gt;
            guide.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            guide.classList.add(&#039;main-guide-screen&#039;);&lt;br /&gt;
            if (!guide.id) guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        ensureGuideDummyMenu(guide);&lt;br /&gt;
&lt;br /&gt;
        panels = Array.prototype.slice.call(&lt;br /&gt;
            layout.well.querySelectorAll(&#039;.main-body-screen[data-main-body-screen]&#039;)&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (active !== &#039;guide&#039;) active = &#039;manifesto&#039;;&lt;br /&gt;
        if (!panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === active;&lt;br /&gt;
        })) {&lt;br /&gt;
            active = &#039;manifesto&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(&lt;br /&gt;
                panel,&lt;br /&gt;
                panel.getAttribute(&#039;data-main-body-screen&#039;) !== active&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        layout.well.setAttribute(&#039;data-main-body-screen-controller-ready&#039;, &#039;1&#039;);&lt;br /&gt;
        layout.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; !guide.classList.contains(&#039;is-guide-menu-entering&#039;)) {&lt;br /&gt;
            restartGuideMenuAnimation(guide);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            well: layout.well,&lt;br /&gt;
            panels: panels,&lt;br /&gt;
            manifesto: manifesto,&lt;br /&gt;
            guide: guide,&lt;br /&gt;
            active: active&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBodyScreen(portal, screenName) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var previous;&lt;br /&gt;
        var active;&lt;br /&gt;
        var event;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        screenName = screenName === &#039;guide&#039; ? &#039;guide&#039; : &#039;manifesto&#039;;&lt;br /&gt;
        if (!screens.panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === screenName;&lt;br /&gt;
        })) return false;&lt;br /&gt;
&lt;br /&gt;
        previous = screens.active;&lt;br /&gt;
        active = screenName;&lt;br /&gt;
&lt;br /&gt;
        screens.panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(&lt;br /&gt;
                panel,&lt;br /&gt;
                panel.getAttribute(&#039;data-main-body-screen&#039;) !== active&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        screens.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; previous !== active) {&lt;br /&gt;
            restartGuideMenuAnimation(screens.guide);&lt;br /&gt;
        } else if (active !== &#039;guide&#039; &amp;amp;&amp;amp; screens.guide) {&lt;br /&gt;
            screens.guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            animateGuideLayout(portal, active === &#039;guide&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            try {&lt;br /&gt;
                event = new CustomEvent(&#039;mainbodyscreenchange&#039;, {&lt;br /&gt;
                    bubbles: true,&lt;br /&gt;
                    detail: { screen: active, previous: previous }&lt;br /&gt;
                });&lt;br /&gt;
                portal.dispatchEvent(event);&lt;br /&gt;
            } catch (error) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function toggleBodyScreen(portal) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        target = screens.active === &#039;guide&#039; ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        return activateBodyScreen(portal, target);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleGuideScreenCapture(event) {&lt;br /&gt;
        var target;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var portal;&lt;br /&gt;
&lt;br /&gt;
        if (!event || event.__mainGuideScreenHandled) return;&lt;br /&gt;
        if (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        target = event.target;&lt;br /&gt;
        anchor = target &amp;amp;&amp;amp; target.closest&lt;br /&gt;
            ? target.closest(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)&lt;br /&gt;
            : null;&lt;br /&gt;
        if (!anchor) return;&lt;br /&gt;
&lt;br /&gt;
        portal = anchor.closest ? anchor.closest(&#039;.main-portal&#039;) : null;&lt;br /&gt;
        if (!portal || !toggleBodyScreen(portal)) return;&lt;br /&gt;
&lt;br /&gt;
        event.__mainGuideScreenHandled = true;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) {&lt;br /&gt;
            event.stopImmediatePropagation();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    이전 실행본의 ResizeObserver가 오래된 문서 링크를 다시 그려도&lt;br /&gt;
    전역 SPA 링크 처리보다 먼저 화면 전환으로 고정한다.&lt;br /&gt;
    */&lt;br /&gt;
    if (window.BottomGuideNavScreenCaptureHandler) {&lt;br /&gt;
        document.removeEventListener(&#039;click&#039;, window.BottomGuideNavScreenCaptureHandler, true);&lt;br /&gt;
    }&lt;br /&gt;
    window.BottomGuideNavScreenCaptureHandler = handleGuideScreenCapture;&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleGuideScreenCapture, true);&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        ensureBodyScreens(portal);&lt;br /&gt;
&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
            mount.setAttribute(&#039;data-bottom-guide-generated&#039;, &#039;1&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        길라잡이는 우물 안의 독립 버튼이 아니라&lt;br /&gt;
        main-body-panel 하단 프레임에 융합된 돌출부다.&lt;br /&gt;
        */&lt;br /&gt;
        if (mount.parentNode !== layout.panel) {&lt;br /&gt;
            layout.panel.appendChild(mount);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(portal, suppliedGeometry) {&lt;br /&gt;
        var mount = ensureMount(portal);&lt;br /&gt;
        var width = geometryApi().readWidth(mount);&lt;br /&gt;
        var guideActive = portal.getAttribute(&#039;data-main-body-screen&#039;) === &#039;guide&#039;;&lt;br /&gt;
        var geometry = suppliedGeometry || portal.GuideNavGeometry || getGuideGeometry(guideActive);&lt;br /&gt;
&lt;br /&gt;
        if (!width) return false;&lt;br /&gt;
&lt;br /&gt;
        portal.GuideNavGeometry = geometry;&lt;br /&gt;
&lt;br /&gt;
        var button = buildGuideButton(geometry);&lt;br /&gt;
        var points;&lt;br /&gt;
        var surfacePoints;&lt;br /&gt;
        var insetPoints;&lt;br /&gt;
        var center;&lt;br /&gt;
&lt;br /&gt;
        if (!button) return false;&lt;br /&gt;
        points = button.points;&lt;br /&gt;
        surfacePoints = guideLayer(button, geometry, geometryApi().FRAME.BORDER);&lt;br /&gt;
        insetPoints = guideLayer(&lt;br /&gt;
            button,&lt;br /&gt;
            geometry,&lt;br /&gt;
            geometryApi().FRAME.BORDER + geometryApi().FRAME.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
        center = centerOfPoints(surfacePoints);&lt;br /&gt;
        var nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        var svg = createSvgElement(&#039;svg&#039;);&lt;br /&gt;
        var anchor = createSvgElement(&#039;a&#039;);&lt;br /&gt;
        var buttonGeometry = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var targetScreen = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
        var text = createSvgElement(&#039;text&#039;);&lt;br /&gt;
        var outer = createPolygon(&#039;portal-cat-edge&#039;, points);&lt;br /&gt;
        var surface = createPolygon(&#039;portal-cat-surface&#039;, surfacePoints);&lt;br /&gt;
        var hit = createPolygon(&#039;portal-cat-hit&#039;, points);&lt;br /&gt;
&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-guide-nav is-continuous-weld&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
&lt;br /&gt;
        svg.setAttribute(&#039;class&#039;, &#039;portal-category-svg portal-guide-svg&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + width + &#039; &#039; + geometry.rowHeight);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, geometry.rowHeight);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        appendContinuousBottomFrame(svg, button, width, geometry);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;class&#039;, &#039;portal-cat-anchor portal-guide-anchor&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        anchor.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, &#039;guide&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-main-body-target&#039;, targetScreen);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-controls&#039;, &#039;main-manifesto-screen main-guide-screen&#039;);&lt;br /&gt;
        buttonGeometry.setAttribute(&#039;class&#039;, &#039;portal-guide-button-geometry&#039;);&lt;br /&gt;
&lt;br /&gt;
        anchor.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            if (&lt;br /&gt;
                event &amp;amp;&amp;amp;&lt;br /&gt;
                (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (toggleBodyScreen(portal)) {&lt;br /&gt;
                if (event) event.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        outer.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        surface.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;class&#039;, &#039;portal-cat-label portal-guide-label&#039;);&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, Math.round(center[0]));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, Math.round((geometry.buttonTop + geometry.buttonBottom) / 2) + 1);&lt;br /&gt;
        text.setAttribute(&#039;font-size&#039;, &#039;12&#039;);&lt;br /&gt;
        text.setAttribute(&#039;font-weight&#039;, &#039;700&#039;);&lt;br /&gt;
        text.setAttribute(&#039;text-anchor&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;dominant-baseline&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        text.textContent = label;&lt;br /&gt;
&lt;br /&gt;
        buttonGeometry.appendChild(outer);&lt;br /&gt;
        buttonGeometry.appendChild(surface);&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-top&#039;,&lt;br /&gt;
            insetPoints[0],&lt;br /&gt;
            insetPoints[1]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-right&#039;,&lt;br /&gt;
            insetPoints[1],&lt;br /&gt;
            insetPoints[2]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-bottom&#039;,&lt;br /&gt;
            insetPoints[2],&lt;br /&gt;
            insetPoints[3]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-left&#039;,&lt;br /&gt;
            insetPoints[3],&lt;br /&gt;
            insetPoints[0]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(hit);&lt;br /&gt;
        anchor.appendChild(buttonGeometry);&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        svg.appendChild(anchor);&lt;br /&gt;
        nav.appendChild(svg);&lt;br /&gt;
&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        applyGuideRowHeight(mount, nav, svg, geometry);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-width&#039;, String(width));&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findPortal(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, attempt) {&lt;br /&gt;
        window.clearTimeout(retryTimer);&lt;br /&gt;
&lt;br /&gt;
        retryTimer = window.setTimeout(function () {&lt;br /&gt;
            var portal = findPortal(root);&lt;br /&gt;
&lt;br /&gt;
            if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
            if (!render(portal) &amp;amp;&amp;amp; attempt &amp;lt; 20) {&lt;br /&gt;
                schedule(root, attempt + 1);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (observer) observer.disconnect();&lt;br /&gt;
&lt;br /&gt;
            if (window.ResizeObserver) {&lt;br /&gt;
                observer = new ResizeObserver(function () {&lt;br /&gt;
                    window.clearTimeout(resizeTimer);&lt;br /&gt;
                    resizeTimer = window.setTimeout(function () {&lt;br /&gt;
                        render(portal);&lt;br /&gt;
                    }, 60);&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                observer.observe(portal);&lt;br /&gt;
                var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
                var guideMount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
                if (topMount) observer.observe(topMount);&lt;br /&gt;
                if (guideMount) observer.observe(guideMount);&lt;br /&gt;
            }&lt;br /&gt;
        }, attempt ? 60 : 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot(root) {&lt;br /&gt;
        var scope = root || document;&lt;br /&gt;
        schedule(scope, 0);&lt;br /&gt;
        loadManifestoIntro(scope);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {&lt;br /&gt;
            boot(document);&lt;br /&gt;
        }, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* 일반 링크 진입과 새로고침의 최종 로드 시점도 명시적으로 보장한다. */&lt;br /&gt;
    window.addEventListener(&#039;load&#039;, function () {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }, { once: true });&lt;br /&gt;
&lt;br /&gt;
    /* bfcache 복원과 브라우저 뒤로/앞으로 이동에서도 새 DOM만 초기화한다. */&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, function () {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            boot($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BottomGuideNav = {&lt;br /&gt;
        version: &#039;20260717-shared-frame-reverse-slant-001&#039;,&lt;br /&gt;
        render: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? render(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        activate: function (screenName) {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? activateBodyScreen(portal, screenName) : false;&lt;br /&gt;
        },&lt;br /&gt;
        toggle: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? toggleBodyScreen(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        status: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            var mount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
            return {&lt;br /&gt;
                loaded: true,&lt;br /&gt;
                portal: !!portal,&lt;br /&gt;
                mount: !!mount,&lt;br /&gt;
                rendered: !!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-svg&#039;)),&lt;br /&gt;
                generated: !!(mount &amp;amp;&amp;amp; mount.getAttribute(&#039;data-bottom-guide-generated&#039;)),&lt;br /&gt;
                panel: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;)),&lt;br /&gt;
                well: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-well&#039;)),&lt;br /&gt;
                insidePanel: !!(mount &amp;amp;&amp;amp; mount.parentNode &amp;amp;&amp;amp; mount.parentNode.classList.contains(&#039;main-body-panel&#039;)),&lt;br /&gt;
                welded: !!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-nav.is-continuous-weld&#039;)),&lt;br /&gt;
                activeScreen: portal ? portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039; : &#039;&#039;,&lt;br /&gt;
                guideScreen: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;)),&lt;br /&gt;
                width: mount ? mount.getAttribute(&#039;data-bottom-guide-width&#039;) || &#039;&#039; : &#039;&#039;&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:PortalSectionNav.js&amp;diff=4670</id>
		<title>미디어위키:PortalSectionNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:PortalSectionNav.js&amp;diff=4670"/>
		<updated>2026-07-17T02:35:53Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-display-fix-20260717 / js/PortalSectionNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
PortalSectionNav&lt;br /&gt;
대문 상단 카테고리 하단 부착형 섹션 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var COMPONENT = &#039;portal-section-nav&#039;;&lt;br /&gt;
    var VERSION = &#039;20260717-shared-frame-phase-001&#039;;&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
    var ITEMS = [&lt;br /&gt;
        { key:&#039;black-ice&#039;, label:&#039;블랙 아이스&#039;, title:&#039;대문&#039; },&lt;br /&gt;
        { key:&#039;anecdote&#039;, label:&#039;에넥도트&#039;, href:&#039;/index.php/특수:모든문서?namespace=3000&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function api() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildWikiPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) return window.buildWikiPath(title);&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal, topMount) {&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
        }&lt;br /&gt;
        if (mount.parentNode !== topMount.parentNode || topMount.nextSibling !== mount) {&lt;br /&gt;
            topMount.parentNode.insertBefore(mount, topMount.nextSibling);&lt;br /&gt;
        }&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function outerGeometry(seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var phase = seams.bottomStartAbsY % frame.FRAME.SLANT_RISE;&lt;br /&gt;
        var leftSeamX = frame.slantX(&lt;br /&gt;
            seams.settingsLeftTopX,&lt;br /&gt;
            seams.bottomStartAbsY,&lt;br /&gt;
            seams.settingsLeftDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        var rightSeamX = frame.slantX(&lt;br /&gt;
            seams.corpRightTopX,&lt;br /&gt;
            seams.bottomStartAbsY,&lt;br /&gt;
            seams.corpRightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            phase:phase,&lt;br /&gt;
            leftSeamX:leftSeamX,&lt;br /&gt;
            rightSeamX:rightSeamX,&lt;br /&gt;
            leftDir:seams.settingsLeftDir,&lt;br /&gt;
            rightDir:seams.corpRightDir,&lt;br /&gt;
            points:frame.segment(&lt;br /&gt;
                leftSeamX,&lt;br /&gt;
                rightSeamX,&lt;br /&gt;
                d.ROW_HEIGHT,&lt;br /&gt;
                seams.settingsLeftDir,&lt;br /&gt;
                seams.corpRightDir,&lt;br /&gt;
                phase&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendFrame(svg, geometry) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var f = frame.FRAME;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var group = frame.create(&#039;g&#039;, &#039;portal-section-frame&#039;);&lt;br /&gt;
        var surface = frame.segmentAt(&lt;br /&gt;
            geometry.leftSeamX,&lt;br /&gt;
            geometry.rightSeamX,&lt;br /&gt;
            f.BORDER,&lt;br /&gt;
            d.ROW_HEIGHT - f.BORDER,&lt;br /&gt;
            geometry.leftDir,&lt;br /&gt;
            geometry.rightDir,&lt;br /&gt;
            geometry.phase,&lt;br /&gt;
            f.BORDER,&lt;br /&gt;
            f.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var well = frame.segmentAt(&lt;br /&gt;
            geometry.leftSeamX,&lt;br /&gt;
            geometry.rightSeamX,&lt;br /&gt;
            f.FRAME_FACE,&lt;br /&gt;
            d.ROW_HEIGHT - f.FRAME_FACE,&lt;br /&gt;
            geometry.leftDir,&lt;br /&gt;
            geometry.rightDir,&lt;br /&gt;
            geometry.phase,&lt;br /&gt;
            f.FRAME_FACE,&lt;br /&gt;
            f.FRAME_FACE&lt;br /&gt;
        );&lt;br /&gt;
        var joinCover = frame.segmentAt(&lt;br /&gt;
            geometry.leftSeamX,&lt;br /&gt;
            geometry.rightSeamX,&lt;br /&gt;
            0,&lt;br /&gt;
            d.OVERLAP,&lt;br /&gt;
            geometry.leftDir,&lt;br /&gt;
            geometry.rightDir,&lt;br /&gt;
            geometry.phase,&lt;br /&gt;
            0,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        var sideBevel = frame.segmentAt(&lt;br /&gt;
            geometry.leftSeamX,&lt;br /&gt;
            geometry.rightSeamX,&lt;br /&gt;
            d.OVERLAP,&lt;br /&gt;
            d.ROW_HEIGHT - f.BORDER,&lt;br /&gt;
            geometry.leftDir,&lt;br /&gt;
            geometry.rightDir,&lt;br /&gt;
            geometry.phase,&lt;br /&gt;
            f.BORDER + f.BEVEL,&lt;br /&gt;
            f.BORDER + f.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-section-frame-edge&#039;, geometry.points));&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-section-frame-surface&#039;, surface));&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-section-frame-well&#039;, well));&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-section-join-side-cover&#039;, joinCover));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-frame-shadow&#039;, sideBevel[3], sideBevel[0]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-frame-highlight&#039;, sideBevel[1], sideBevel[2]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-well-shadow&#039;, well[0], well[1]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-well-shadow&#039;, well[1], well[2]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-well-highlight&#039;, well[3], well[0]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-section-well-highlight&#039;, well[2], well[3]));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function splitButtons(geometry) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var f = frame.FRAME;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var leftBase = geometry.leftSeamX + f.FRAME_FACE + f.GAP;&lt;br /&gt;
        var rightBase = geometry.rightSeamX - f.FRAME_FACE - f.GAP;&lt;br /&gt;
        var leftTop = frame.slantX(leftBase, d.BUTTON_TOP, geometry.leftDir, geometry.phase);&lt;br /&gt;
        var rightTop = frame.slantX(rightBase, d.BUTTON_TOP, geometry.rightDir, geometry.phase);&lt;br /&gt;
        var topMid = Math.round((leftTop + rightTop) / 2);&lt;br /&gt;
        var halfGap = f.GAP / 2;&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                leftTopX:leftBase,&lt;br /&gt;
                rightTopX:topMid - halfGap,&lt;br /&gt;
                leftDir:geometry.leftDir,&lt;br /&gt;
                rightDir:0,&lt;br /&gt;
                phase:geometry.phase&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                leftTopX:topMid + halfGap,&lt;br /&gt;
                rightTopX:rightBase,&lt;br /&gt;
                leftDir:0,&lt;br /&gt;
                rightDir:geometry.rightDir,&lt;br /&gt;
                phase:geometry.phase&lt;br /&gt;
            }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendButton(svg, item, button) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var f = frame.FRAME;&lt;br /&gt;
        var d = frame.DIMENSIONS;&lt;br /&gt;
        var points = frame.segmentAt(&lt;br /&gt;
            button.leftTopX,&lt;br /&gt;
            button.rightTopX,&lt;br /&gt;
            d.BUTTON_TOP,&lt;br /&gt;
            d.BUTTON_BOTTOM,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            button.phase,&lt;br /&gt;
            0,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        var surface = frame.segmentAt(&lt;br /&gt;
            button.leftTopX,&lt;br /&gt;
            button.rightTopX,&lt;br /&gt;
            d.BUTTON_TOP + f.BORDER,&lt;br /&gt;
            d.BUTTON_BOTTOM - f.BORDER,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            button.phase,&lt;br /&gt;
            f.BORDER,&lt;br /&gt;
            f.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var bevel = frame.segmentAt(&lt;br /&gt;
            button.leftTopX,&lt;br /&gt;
            button.rightTopX,&lt;br /&gt;
            d.BUTTON_TOP + f.BORDER + f.BEVEL,&lt;br /&gt;
            d.BUTTON_BOTTOM - f.BORDER - f.BEVEL,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            button.phase,&lt;br /&gt;
            f.BORDER + f.BEVEL,&lt;br /&gt;
            f.BORDER + f.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
        var anchor = frame.create(&#039;a&#039;, &#039;portal-cat-anchor portal-section-anchor&#039;);&lt;br /&gt;
        var group = frame.create(&#039;g&#039;, &#039;portal-section-button-geometry&#039;);&lt;br /&gt;
        var hit = frame.polygon(&#039;portal-cat-hit&#039;, points);&lt;br /&gt;
        var text = frame.create(&#039;text&#039;, &#039;portal-cat-label portal-section-label&#039;);&lt;br /&gt;
        var href = item.href || buildWikiPath(item.title);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, item.key);&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-cat-edge&#039;, points));&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-cat-surface&#039;, surface));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-cat-inset portal-cat-inset-top&#039;, bevel[0], bevel[1]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-cat-inset portal-cat-inset-right&#039;, bevel[1], bevel[2]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-cat-inset portal-cat-inset-bottom&#039;, bevel[2], bevel[3]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-cat-inset portal-cat-inset-left&#039;, bevel[3], bevel[0]));&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
        group.appendChild(hit);&lt;br /&gt;
        anchor.appendChild(group);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, frame.centerX(surface));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, Math.round((d.BUTTON_TOP + d.BUTTON_BOTTOM) / 2) + 1);&lt;br /&gt;
        text.setAttribute(&#039;font-size&#039;, &#039;12&#039;);&lt;br /&gt;
        text.setAttribute(&#039;font-weight&#039;, &#039;700&#039;);&lt;br /&gt;
        text.setAttribute(&#039;text-anchor&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;dominant-baseline&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.textContent = item.label;&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        svg.appendChild(anchor);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPortal(portal, seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var current = seams || (frame &amp;amp;&amp;amp; frame.seams);&lt;br /&gt;
        var topMount;&lt;br /&gt;
        var mount;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var svg;&lt;br /&gt;
        var geometry;&lt;br /&gt;
        var buttons;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !portal || !current || !current.width) return false;&lt;br /&gt;
        topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        if (!topMount) return false;&lt;br /&gt;
        mount = ensureMount(portal, topMount);&lt;br /&gt;
        geometry = outerGeometry(current);&lt;br /&gt;
        buttons = splitButtons(geometry);&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        nav.className = &#039;portal-section-nav&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, &#039;작품 영역&#039;);&lt;br /&gt;
        svg = frame.create(&#039;svg&#039;, &#039;portal-section-svg&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + current.width + &#039; &#039; + frame.DIMENSIONS.ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, frame.DIMENSIONS.ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;none&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;data-frame-width&#039;, current.width);&lt;br /&gt;
&lt;br /&gt;
        appendFrame(svg, geometry);&lt;br /&gt;
        ITEMS.forEach(function (item, index) { appendButton(svg, item, buttons[index]); });&lt;br /&gt;
        nav.appendChild(svg);&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-width&#039;, String(current.width));&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-phase&#039;, String(geometry.phase));&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectPortals(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var portals = [];&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)) portals.push(scope);&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;.main-portal&#039;), function (portal) {&lt;br /&gt;
            if (portals.indexOf(portal) === -1) portals.push(portal);&lt;br /&gt;
        });&lt;br /&gt;
        return portals;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root, seams) {&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
        collectPortals(root).forEach(function (portal) {&lt;br /&gt;
            if (renderPortal(portal, seams)) rendered += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, seams) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document, seams);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (api()) api().onSeams(function (seams) { schedule(document, seams); });&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            schedule(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.PortalSectionNav = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        render:renderPortal,&lt;br /&gt;
        renderAll:renderAll&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4669</id>
		<title>미디어위키:Common.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4669"/>
		<updated>2026-07-17T02:35:53Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-display-fix-20260717 / js/Common.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;mw.loader.load(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* Raw MediaWiki script cache key.&lt;br /&gt;
   This is not a semantic version. It only prevents stale raw JS during active editing. */&lt;br /&gt;
window.CLBI_RAW_LOAD_BUST = &#039;portal-frame-display-fix-20260717-002&#039;;&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Raw script loads are tracked so the site boot gate can wait for real subsystem scripts&lt;br /&gt;
before releasing the normal surface.  The old prefixed load-bust variable remains for&lt;br /&gt;
compatibility only; new loader state uses unprefixed EntryScriptLoads/EntryRawScriptPromises.&lt;br /&gt;
*/&lt;br /&gt;
window.EntryScriptLoads = window.EntryScriptLoads || [];&lt;br /&gt;
window.EntryRawScriptPromises = window.EntryRawScriptPromises || {};&lt;br /&gt;
function buildEntryRawScriptUrl(key) {&lt;br /&gt;
    var url = &#039;/index.php?title=&#039; + encodeURIComponent(key) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;;&lt;br /&gt;
    var token;&lt;br /&gt;
    var sep;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, key);&lt;br /&gt;
        }&lt;br /&gt;
        if (/[?&amp;amp;]_entryRev=/.test(url)) return url;&lt;br /&gt;
        token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(&#039;raw-script-fallback&#039;) : window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        token = window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
    return url + sep + &#039;v=&#039; + encodeURIComponent(String(token || &#039;entry-raw&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadClbiRawScript(title) {&lt;br /&gt;
    var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
    var promise;&lt;br /&gt;
&lt;br /&gt;
    if (!key) return Promise.resolve(false);&lt;br /&gt;
    if (window.EntryRawScriptPromises[key]) return window.EntryRawScriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
    promise = Promise.resolve()&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.ensureLoaded === &#039;function&#039;) {&lt;br /&gt;
                return window.RevisionManifest.ensureLoaded();&lt;br /&gt;
            }&lt;br /&gt;
            return null;&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function () { return null; })&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
                script.src = buildEntryRawScriptUrl(key);&lt;br /&gt;
                script.async = true;&lt;br /&gt;
                script.setAttribute(&#039;data-entry-raw-script&#039;, key);&lt;br /&gt;
                script.onload = function () { resolve({ title: key, ok: true, src: script.src }); };&lt;br /&gt;
                script.onerror = function () { resolve({ title: key, ok: false, src: script.src }); };&lt;br /&gt;
                (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    window.EntryRawScriptPromises[key] = promise;&lt;br /&gt;
    window.EntryScriptLoads.push(promise);&lt;br /&gt;
    return promise;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Main-page fresh assets with fallback&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css의 MainPage.css import는 안전망으로 그대로 유지한다.&lt;br /&gt;
대문에서는 새로고침마다 timestamp가 붙은 CSS/JS를 추가로 불러와&lt;br /&gt;
브라우저·프록시의 오래된 raw 응답을 우회한다.&lt;br /&gt;
&lt;br /&gt;
이 보조 로더가 실패해도 기존 정적 import와 기존 화면은 남는다.&lt;br /&gt;
*/&lt;br /&gt;
window.MainPageFreshAssets = window.MainPageFreshAssets || (function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var sessionToken = String(Date.now());&lt;br /&gt;
    var ASSET_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var scriptPromises = {};&lt;br /&gt;
    var styleNode = null;&lt;br /&gt;
&lt;br /&gt;
    function isMainPage() {&lt;br /&gt;
        var page = &#039;&#039;;&lt;br /&gt;
        try {&lt;br /&gt;
            page = String(mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;);&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return page.replace(/_/g, &#039; &#039;) === &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrl(title, type) {&lt;br /&gt;
        var ctype = type === &#039;style&#039; ? &#039;text/css&#039; : &#039;text/javascript&#039;;&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) +&lt;br /&gt;
            &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype) +&lt;br /&gt;
            &#039;&amp;amp;_mainPageFresh=&#039; + encodeURIComponent(sessionToken);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadStyle() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        if (styleNode &amp;amp;&amp;amp; styleNode.parentNode) {&lt;br /&gt;
            return Promise.resolve(true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var link = document.createElement(&#039;link&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            link.rel = &#039;stylesheet&#039;;&lt;br /&gt;
            link.href = rawUrl(&#039;MediaWiki:MainPage.css&#039;, &#039;style&#039;);&lt;br /&gt;
            link.setAttribute(&#039;data-main-page-fresh-style&#039;, sessionToken);&lt;br /&gt;
            link.onload = function () {&lt;br /&gt;
                styleNode = link;&lt;br /&gt;
                settle(true);&lt;br /&gt;
            };&lt;br /&gt;
            link.onerror = function () {&lt;br /&gt;
                /* Common.css의 정적 import가 그대로 남아 있으므로 화면은 유지된다. */&lt;br /&gt;
                settle(false);&lt;br /&gt;
            };&lt;br /&gt;
            timeout = window.setTimeout(function () { settle(false); }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(link);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadScript(title) {&lt;br /&gt;
        var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!key || !isMainPage()) return Promise.resolve(false);&lt;br /&gt;
        if (scriptPromises[key]) return scriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
        scriptPromises[key] = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            script.src = rawUrl(key, &#039;script&#039;);&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-main-page-fresh-script&#039;, key);&lt;br /&gt;
            script.onload = function () { settle(true); };&lt;br /&gt;
            script.onerror = function () { settle(false); };&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return scriptPromises[key];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensure() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        return loadStyle()&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:FrameGeometry.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryPillar.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:PortalSectionNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:BottomGuideNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (window.CategoryNav &amp;amp;&amp;amp; typeof window.CategoryNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.CategoryPillar &amp;amp;&amp;amp; typeof window.CategoryPillar.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryPillar.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.PortalSectionNav &amp;amp;&amp;amp; typeof window.PortalSectionNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.PortalSectionNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.render === &#039;function&#039;) {&lt;br /&gt;
                    window.BottomGuideNav.render();&lt;br /&gt;
                }&lt;br /&gt;
                return true;&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function status() {&lt;br /&gt;
        return {&lt;br /&gt;
            mainPage: isMainPage(),&lt;br /&gt;
            token: sessionToken,&lt;br /&gt;
            style: !!document.querySelector(&#039;link[data-main-page-fresh-style]&#039;),&lt;br /&gt;
            geometry: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:FrameGeometry.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            category: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            pillar: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryPillar.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            section: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:PortalSectionNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            guide: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:BottomGuideNav.js&amp;quot;]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            ensure();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(ensure, 0);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        ensure: ensure,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Account access page marker&lt;br /&gt;
   =========================================&lt;br /&gt;
   New shared systems use unprefixed names.  The server body class differs&lt;br /&gt;
   between skins/locales, so the canonical special-page name is normalized&lt;br /&gt;
   once and exposed as a stable page-state class for the login surface.&lt;br /&gt;
*/&lt;br /&gt;
(function markAccountLoginPage(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var canonical = &#039;&#039;;&lt;br /&gt;
    var pageName = &#039;&#039;;&lt;br /&gt;
    var isLoginPage = false;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        canonical = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039; : &#039;&#039;).toLowerCase();&lt;br /&gt;
        pageName = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    isLoginPage = canonical === &#039;userlogin&#039; || /^(?:special|특수):(?:userlogin|로그인)$/.test(pageName);&lt;br /&gt;
    if (!isLoginPage) return;&lt;br /&gt;
&lt;br /&gt;
    document.documentElement.classList.add(&#039;account-login-page-root&#039;);&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountTarget(link) {&lt;br /&gt;
        var href = &#039;&#039;;&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var text = &#039;&#039;;&lt;br /&gt;
        var sample = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!link) return false;&lt;br /&gt;
        href = String(link.getAttribute(&#039;href&#039;) || &#039;&#039;);&lt;br /&gt;
        title = String(link.getAttribute(&#039;title&#039;) || &#039;&#039;);&lt;br /&gt;
        text = String(link.textContent || &#039;&#039;);&lt;br /&gt;
        try { href = decodeURIComponent(href); } catch (err) {}&lt;br /&gt;
        sample = (href + &#039; &#039; + title + &#039; &#039; + text).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        return /(?:special|특수)\s*[:%]\s*(?:createaccount|계정\s*(?:만들기|생성))|createaccount|계정\s*(?:만들기|생성)/i.test(sample);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function suppressCreateAccountSurface(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var fixedSelectors = [&lt;br /&gt;
            &#039;.mw-createacct-benefits-container&#039;,&lt;br /&gt;
            &#039;.mw-createacct-benefits-list&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-cta&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;#mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;.mw-userlogin-create&#039;&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        fixedSelectors.forEach(function (selector) {&lt;br /&gt;
            Array.prototype.forEach.call(scope.querySelectorAll(selector), function (node) {&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                node.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;a[href]&#039;), function (link) {&lt;br /&gt;
            var container;&lt;br /&gt;
            if (!isCreateAccountTarget(link)) return;&lt;br /&gt;
&lt;br /&gt;
            link.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            link.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            link.setAttribute(&#039;tabindex&#039;, &#039;-1&#039;);&lt;br /&gt;
&lt;br /&gt;
            container = link.closest(&#039;.mw-ui-vform-field, .oo-ui-fieldLayout, .mw-userlogin-create, p, li&#039;);&lt;br /&gt;
            if (container &amp;amp;&amp;amp; container.querySelectorAll(&#039;a&#039;).length === 1) {&lt;br /&gt;
                container.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                container.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyMarker() {&lt;br /&gt;
        if (!document.body) return;&lt;br /&gt;
        document.body.classList.add(&#039;account-login-page&#039;);&lt;br /&gt;
        suppressCreateAccountSurface(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, applyMarker, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyMarker();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                suppressCreateAccountSurface(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Boot gate prelude&lt;br /&gt;
   =========================================&lt;br /&gt;
   This tiny prelude runs before the full EntryStore/EntryLoader/BootGate implementation.&lt;br /&gt;
   Its job is only to guarantee that the user sees the separate loading surface before the&lt;br /&gt;
   normal wiki shell can paint.  The full BootGate later adopts the same DOM node and keeps&lt;br /&gt;
   it open until the real full-entry readiness contract is satisfied.&lt;br /&gt;
&lt;br /&gt;
   Maintenance rule:&lt;br /&gt;
   - Do not move first-screen hiding into a late subsystem callback.  If the boot gate is&lt;br /&gt;
     meant to protect the first impression, the surface must be activated before sidebars,&lt;br /&gt;
     navbars, document wells, nations panels, or other entry surfaces can visibly fill in.&lt;br /&gt;
   - New names remain unprefixed.  Existing prefixed names elsewhere are legacy aliases only.&lt;br /&gt;
*/&lt;br /&gt;
(function installBootGatePrelude(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SCREEN_ID = &#039;boot-gate-screen&#039;;&lt;br /&gt;
    var STYLE_ID = &#039;boot-gate-prelude-style&#039;;&lt;br /&gt;
    var START_TIME = Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getMwConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.mw &amp;amp;&amp;amp; window.mw.config &amp;amp;&amp;amp; typeof window.mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = window.mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDeveloperOrEditingPage() {&lt;br /&gt;
        var ns = Number(getMwConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(getMwConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(getMwConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Developer/system pages must stay practical.  Editing MediaWiki:, File:,&lt;br /&gt;
         * Project:, Template:, Module:, Category:, or any non-view action already forces&lt;br /&gt;
         * a full page load in MediaWiki, so showing the entry boot screen there only slows&lt;br /&gt;
         * maintenance work and does not improve the public first impression.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = getMwConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(getMwConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(getMwConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var anonymousUser = isAnonymousUser();&lt;br /&gt;
    var disabled = isAuthenticationPage() || (!anonymousUser &amp;amp;&amp;amp; (hasQueryFlag(&#039;bootGate&#039;, &#039;0&#039;) || isDeveloperOrEditingPage()));&lt;br /&gt;
&lt;br /&gt;
    function injectStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (disabled || document.getElementById(STYLE_ID)) return;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = STYLE_ID;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active,body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt;:not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen{position:fixed!important;inset:0!important;z-index:2147483000!important;display:flex!important;align-items:center!important;justify-content:center!important;background:#080808!important;color:#d8d8d8!important;opacity:1;pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-decoration-layer{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-panel{position:relative;z-index:2;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-close{display:none;position:absolute;right:7px;top:6px;z-index:3;height:18px;min-width:18px;border:1px solid #000;background:#141414;color:#ddd;font-size:11px;line-height:16px;padding:0 5px;cursor:pointer;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview{inset:auto!important;left:18px!important;top:18px!important;width:min(720px,calc(100vw - 380px))!important;height:360px!important;z-index:99990!important;overflow:hidden!important;border:1px solid #000!important;box-shadow:0 10px 28px rgba(0,0,0,.55)!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-panel,#boot-gate-screen.is-preview .boot-gate-close{pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-close{display:block;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activate() {&lt;br /&gt;
        if (disabled) return;&lt;br /&gt;
        injectStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeNode() {&lt;br /&gt;
        var node;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var status;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var fill;&lt;br /&gt;
        var progress;&lt;br /&gt;
        var detail;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
&lt;br /&gt;
        if (disabled || !document.body) return null;&lt;br /&gt;
        activate();&lt;br /&gt;
&lt;br /&gt;
        node = document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) return node;&lt;br /&gt;
&lt;br /&gt;
        node = document.createElement(&#039;div&#039;);&lt;br /&gt;
        node.id = SCREEN_ID;&lt;br /&gt;
        node.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
        node.setAttribute(&#039;data-boot-gate-prelude&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        title.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        status = document.createElement(&#039;div&#039;);&lt;br /&gt;
        status.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        status.textContent = &#039;Preparing site entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        fill = document.createElement(&#039;div&#039;);&lt;br /&gt;
        fill.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(fill);&lt;br /&gt;
&lt;br /&gt;
        progress = document.createElement(&#039;div&#039;);&lt;br /&gt;
        progress.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        progress.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        detail = document.createElement(&#039;div&#039;);&lt;br /&gt;
        detail.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        detail.textContent = &#039;boot gate prelude&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { release(node); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(title);&lt;br /&gt;
        panel.appendChild(status);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(progress);&lt;br /&gt;
        panel.appendChild(detail);&lt;br /&gt;
        node.appendChild(decoLayer);&lt;br /&gt;
        node.appendChild(panel);&lt;br /&gt;
        node.appendChild(close);&lt;br /&gt;
&lt;br /&gt;
        document.body.insertBefore(node, document.body.firstChild || null);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function onBody(callback) {&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            callback();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, callback, { once: true });&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function tick() {&lt;br /&gt;
            if (document.body) return callback();&lt;br /&gt;
            window.setTimeout(tick, 10);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function release(node) {&lt;br /&gt;
        node = node || document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) {&lt;br /&gt;
            node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
            node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            }, 240);&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (document.documentElement) document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    activate();&lt;br /&gt;
    onBody(function () {&lt;br /&gt;
        activate();&lt;br /&gt;
        makeNode();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.__BootGatePrelude = {&lt;br /&gt;
        startTime: START_TIME,&lt;br /&gt;
        activate: activate,&lt;br /&gt;
        ensure: makeNode,&lt;br /&gt;
        release: release,&lt;br /&gt;
        node: function () { return document.getElementById(SCREEN_ID); },&lt;br /&gt;
        disabled: disabled&lt;br /&gt;
    };&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Site boot gate and entry artifact contract&lt;br /&gt;
   =========================================&lt;br /&gt;
   This is the initial-load full/half entry artifact system.&lt;br /&gt;
&lt;br /&gt;
   Purpose:&lt;br /&gt;
   - The loading screen is not decorative. It exists only during the first site entry in a&lt;br /&gt;
     tab, before the user is allowed into the normal wiki surface.&lt;br /&gt;
   - A &amp;quot;full&amp;quot; entry must mean that the first view of that system can appear without an&lt;br /&gt;
     additional visible data load. For the nations system, the 1950 entry is full only when&lt;br /&gt;
     its nation list/link-map data and first-view pixel decorations are ready in this tab.&lt;br /&gt;
   - A &amp;quot;half&amp;quot; entry is a predictive warm state for the next likely path. It may fetch and&lt;br /&gt;
     parse data, but may skip expensive final work such as image/canvas preparation until it&lt;br /&gt;
     is promoted to full.&lt;br /&gt;
   - This principle is a site-wide design priority, like SPA continuity. New viewers,&lt;br /&gt;
     document systems, and information panels should define their entry full/half packs&lt;br /&gt;
     before exposing a first screen that can visibly fill in later.&lt;br /&gt;
   - SPA navigation is a consumer phase, not a blocking phase.  Do not add BootGate holds&lt;br /&gt;
     to SPA route changes.  If a route needs seamless first paint, prepare its artifacts&lt;br /&gt;
     during the initial boot pack and have the subsystem consume EntryStore synchronously&lt;br /&gt;
     before inserting or painting visible DOM.&lt;br /&gt;
&lt;br /&gt;
   Naming rule:&lt;br /&gt;
   - New public APIs, globals, classes, functions, files, and components must not use a&lt;br /&gt;
     project prefix. Old prefixed globals are legacy compatibility surfaces only.&lt;br /&gt;
   - New code should use names such as BootGate, EntryLoader, EntryStore, and boot-gate-*.&lt;br /&gt;
*/&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var MANIFEST_TITLE = &#039;MediaWiki:EntryManifest.json&#039;;&lt;br /&gt;
    var BUILD_ID = &#039;20260711-existing-account-login-001&#039;;&lt;br /&gt;
    var deferredEntryWarmups = [];&lt;br /&gt;
    var READY_KEY = &#039;boot-gate-ready-version&#039;;&lt;br /&gt;
    var DISMISS_PARAM = &#039;bootGate&#039;;&lt;br /&gt;
    var bootStarted = false;&lt;br /&gt;
    var bootPromise = null;&lt;br /&gt;
    var bootNode = null;&lt;br /&gt;
    var bootStatusNode = null;&lt;br /&gt;
    var bootProgressNode = null;&lt;br /&gt;
    var bootDetailNode = null;&lt;br /&gt;
    var bootFillNode = null;&lt;br /&gt;
    var bootStartTime = 0;&lt;br /&gt;
    var earlyBootStyleInjected = false;&lt;br /&gt;
    var loginGateLocked = false;&lt;br /&gt;
    var loginGateActionNode = null;&lt;br /&gt;
&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.config &amp;amp;&amp;amp; typeof mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBootPageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        return canonical === &#039;createaccount&#039; || /^(?:special|특수):(?:createaccount|계정 ?(?:만들기|생성))/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = readConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(readConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requiresLoginGate() {&lt;br /&gt;
        return isAnonymousUser() &amp;amp;&amp;amp; !isAuthenticationPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBootExcludedPage() {&lt;br /&gt;
        var ns = Number(readConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(readConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(readConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * The public boot gate is for normal reading surfaces.  Developer/system&lt;br /&gt;
         * namespaces and edit/diff/history actions already reload outside SPA, so&lt;br /&gt;
         * blocking them would turn every maintenance save into another entry boot.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var LOGIN_REQUIRED = requiresLoginGate();&lt;br /&gt;
    var BOOT_EXCLUDED_PAGE = isBootExcludedPage() &amp;amp;&amp;amp; !LOGIN_REQUIRED;&lt;br /&gt;
&lt;br /&gt;
    function injectEarlyBootStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (earlyBootStyleInjected || !document.documentElement) return;&lt;br /&gt;
        earlyBootStyleInjected = true;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = &#039;boot-gate-early-style&#039;;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active, body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt; :not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBootSurface() {&lt;br /&gt;
        injectEarlyBootStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!BOOT_EXCLUDED_PAGE) activateBootSurface();&lt;br /&gt;
    else if (window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.release) window.__BootGatePrelude.release();&lt;br /&gt;
&lt;br /&gt;
    var defaultManifest = {&lt;br /&gt;
        version: &#039;20260710-globe-vhs-restore-entry-001&#039;,&lt;br /&gt;
        boot: {&lt;br /&gt;
            minDisplayMs: 950,&lt;br /&gt;
            cachedMinDisplayMs: 350,&lt;br /&gt;
            maxBlockingMs: 15000&lt;br /&gt;
        },&lt;br /&gt;
        initial: {&lt;br /&gt;
            full: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;decorations-registry&#039;,&lt;br /&gt;
                    label: &#039;DECORATION REGISTRY&#039;,&lt;br /&gt;
                    type: &#039;decorations&#039;,&lt;br /&gt;
                    ref: &#039;MediaWiki:Decorations.json&#039;,&lt;br /&gt;
                    page: &#039;시대&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    preparePixels: true&lt;br /&gt;
                },&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1950-entry&#039;,&lt;br /&gt;
                    label: &#039;1950 ERA ENTRY&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    level: &#039;full&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ],&lt;br /&gt;
            half: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1960-half&#039;,&lt;br /&gt;
                    label: &#039;1960 ERA HALF&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1960&#039;,&lt;br /&gt;
                    level: &#039;half&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ]&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function now() {&lt;br /&gt;
        return Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var BootPerf = window.BootPerf = window.BootPerf || (function () {&lt;br /&gt;
        var t0 = now();&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;entry&#039;), start: now(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = now();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = ended - item.start;&lt;br /&gt;
            item.offset = item.start - t0;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            entries.push(item);&lt;br /&gt;
            return item;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function instant(name, meta) {&lt;br /&gt;
            var t = now();&lt;br /&gt;
            entries.push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: t - t0, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measure(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function rows() {&lt;br /&gt;
            return entries.slice().sort(function (a, b) { return a.start - b.start; }).map(function (item) {&lt;br /&gt;
                return {&lt;br /&gt;
                    offset: item.offset,&lt;br /&gt;
                    ms: item.ms,&lt;br /&gt;
                    name: item.name,&lt;br /&gt;
                    meta: item.meta || {}&lt;br /&gt;
                };&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            var total = list.reduce(function (max, item) { return Math.max(max, item.offset + item.ms); }, 0);&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD_ID,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: total,&lt;br /&gt;
                entries: list,&lt;br /&gt;
                active: Object.keys(active).map(function (id) { return active[id]; })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function print() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            if (!window.console || !console.log) return summary();&lt;br /&gt;
            try {&lt;br /&gt;
                console.groupCollapsed(&#039;[BootPerf] entry loading timeline · &#039; + BUILD_ID);&lt;br /&gt;
                if (console.table) console.table(list.map(function (item) {&lt;br /&gt;
                    return {&lt;br /&gt;
                        offset: item.offset + &#039;ms&#039;,&lt;br /&gt;
                        duration: item.ms + &#039;ms&#039;,&lt;br /&gt;
                        name: item.name,&lt;br /&gt;
                        detail: JSON.stringify(item.meta || {})&lt;br /&gt;
                    };&lt;br /&gt;
                }));&lt;br /&gt;
                else list.forEach(function (item) { console.log(item.offset + &#039;ms&#039;, item.ms + &#039;ms&#039;, item.name, item.meta || {}); });&lt;br /&gt;
                console.groupEnd();&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            return summary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: instant,&lt;br /&gt;
            measure: measure,&lt;br /&gt;
            rows: rows,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            print: print&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var InteractionPerf = window.InteractionPerf = window.InteractionPerf || (function () {&lt;br /&gt;
        var BUILD = &#039;20260710-interaction-perf-instrument-001&#039;;&lt;br /&gt;
        var t0 = (window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now());&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
        var maxEntries = 1600;&lt;br /&gt;
&lt;br /&gt;
        function perfNow() {&lt;br /&gt;
            return window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function push(entry) {&lt;br /&gt;
            entries.push(entry);&lt;br /&gt;
            if (entries.length &amp;gt; maxEntries) entries.splice(0, entries.length - maxEntries);&lt;br /&gt;
            return entry;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;interaction&#039;), start: perfNow(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = perfNow();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = Math.round((ended - item.start) * 100) / 100;&lt;br /&gt;
            item.offset = Math.round((item.start - t0) * 100) / 100;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            return push(item);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function mark(name, meta) {&lt;br /&gt;
            var t = perfNow();&lt;br /&gt;
            return push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: Math.round((t - t0) * 100) / 100, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureSync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                var value = fn();&lt;br /&gt;
                end(id, { ok: true });&lt;br /&gt;
                return value;&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                throw err;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureAsync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: Math.round((perfNow() - t0) * 100) / 100,&lt;br /&gt;
                entries: entries.slice().sort(function (a, b) {&lt;br /&gt;
                    if (a.offset !== b.offset) return a.offset - b.offset;&lt;br /&gt;
                    return b.ms - a.ms;&lt;br /&gt;
                }),&lt;br /&gt;
                active: Object.keys(active).map(function (id) {&lt;br /&gt;
                    var item = active[id];&lt;br /&gt;
                    return { offset: Math.round((item.start - t0) * 100) / 100, ms: Math.round((perfNow() - item.start) * 100) / 100, name: item.name, meta: item.meta || {} };&lt;br /&gt;
                })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function table() {&lt;br /&gt;
            var data = summary().entries.map(function (entry) {&lt;br /&gt;
                return { offset: entry.offset, ms: entry.ms, name: entry.name, meta: entry.meta };&lt;br /&gt;
            });&lt;br /&gt;
            if (window.console &amp;amp;&amp;amp; console.table) console.table(data);&lt;br /&gt;
            return data;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.PerformanceObserver &amp;amp;&amp;amp; !window.CLBI_InteractionLongTaskObserverBound) {&lt;br /&gt;
                window.CLBI_InteractionLongTaskObserverBound = true;&lt;br /&gt;
                new PerformanceObserver(function (list) {&lt;br /&gt;
                    list.getEntries().forEach(function (entry) {&lt;br /&gt;
                        mark(&#039;browser long task&#039;, {&lt;br /&gt;
                            ms: Math.round(entry.duration * 100) / 100,&lt;br /&gt;
                            start: Math.round(entry.startTime * 100) / 100,&lt;br /&gt;
                            attribution: entry.attribution &amp;amp;&amp;amp; entry.attribution.length ? entry.attribution.length : 0&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }).observe({ entryTypes: [&#039;longtask&#039;] });&lt;br /&gt;
            }&lt;br /&gt;
        } catch (ignoreLongTaskObserver) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD,&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: mark,&lt;br /&gt;
            measureSync: measureSync,&lt;br /&gt;
            measureAsync: measureAsync,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            table: table&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
    function toArray(value) {&lt;br /&gt;
        return Array.prototype.slice.call(value || []);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function unique(list) {&lt;br /&gt;
        var seen = {};&lt;br /&gt;
        var out = [];&lt;br /&gt;
        (list || []).forEach(function (item) {&lt;br /&gt;
            item = String(item || &#039;&#039;).trim();&lt;br /&gt;
            if (!item || seen[item]) return;&lt;br /&gt;
            seen[item] = true;&lt;br /&gt;
            out.push(item);&lt;br /&gt;
        });&lt;br /&gt;
        return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasBootParam(value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + DISMISS_PARAM + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeTitle(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;#&#039;)[0]&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function extractTitleFromUrl(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;);&lt;br /&gt;
        var match = text.match(/[?&amp;amp;]title=([^&amp;amp;]+)/i);&lt;br /&gt;
        if (match) return normalizeTitle(decodeURIComponent(match[1].replace(/\+/g, &#039; &#039;)));&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeRefKey(ref) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        title = extractTitleFromUrl(text);&lt;br /&gt;
        if (title) return &#039;title:&#039; + title.toLowerCase();&lt;br /&gt;
        if (text.indexOf(&#039;/&#039;) === -1 &amp;amp;&amp;amp; text.indexOf(&#039;:&#039;) !== -1) return &#039;title:&#039; + normalizeTitle(text).toLowerCase();&lt;br /&gt;
        return &#039;url:&#039; + text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrlForRef(ref, ctype) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(text) || text.charAt(0) === &#039;/&#039;) return text;&lt;br /&gt;
        title = normalizeTitle(text.indexOf(&#039;:&#039;) !== -1 ? text : (&#039;MediaWiki:&#039; + text));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            return mw.util.getUrl(title, { action: &#039;raw&#039;, ctype: ctype || &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype || &#039;application/json&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getApiEndpoint() {&lt;br /&gt;
        return (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.wikiScript === &#039;function&#039;) ? mw.util.wikiScript(&#039;api&#039;) : &#039;/api.php&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchApi(params) {&lt;br /&gt;
        var body = new URLSearchParams();&lt;br /&gt;
        Object.keys(params || {}).forEach(function (key) {&lt;br /&gt;
            body.append(key, params[key]);&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(getApiEndpoint(), {&lt;br /&gt;
            method: &#039;POST&#039;,&lt;br /&gt;
            credentials: &#039;same-origin&#039;,&lt;br /&gt;
            headers: { &#039;Content-Type&#039;: &#039;application/x-www-form-urlencoded; charset=UTF-8&#039; },&lt;br /&gt;
            body: body.toString()&lt;br /&gt;
        }).then(function (res) {&lt;br /&gt;
            if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
            return res.json();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =========================================&lt;br /&gt;
       Revision manifest and persistent entry cache&lt;br /&gt;
       =========================================&lt;br /&gt;
       The first boot still prepares current-tab artifacts, but freshness is not guessed from&lt;br /&gt;
       filenames or old localStorage flags.  The client reads a tiny server-side current-state&lt;br /&gt;
       manifest, compares page revisions / file sha1 values, and only keeps cached raw resources&lt;br /&gt;
       whose revision token still matches the server.  The manifest is a latest-state table, not&lt;br /&gt;
       an append-only client log.&lt;br /&gt;
    */&lt;br /&gt;
    var REVISION_MANIFEST_ACTION = &#039;entryrevisionmanifest&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_LOCAL_KEY = &#039;entry-revision-manifest-current-v1&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_PRUNE_LOCAL_KEY = &#039;entry-revision-manifest-last-client-prune-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_NAME = &#039;entry-cache-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_INDEX_KEY = &#039;entry-cache-index-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_REQUEST_PREFIX = &#039;/__entry-cache__/&#039;;&lt;br /&gt;
    var ENTRY_CACHE_PACK_KEY = &#039;entry-cache-pack-state-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
    function hasCacheStorage() {&lt;br /&gt;
        return !!(window.caches &amp;amp;&amp;amp; typeof window.caches.open === &#039;function&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function safeJsonParse(text, fallback) {&lt;br /&gt;
        try { return JSON.parse(text); } catch (err) { return fallback; }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readLocalJson(key, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            var text = window.localStorage ? window.localStorage.getItem(key) : null;&lt;br /&gt;
            return text ? safeJsonParse(text, fallback) : fallback;&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return fallback;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function writeLocalJson(key, value) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.localStorage) window.localStorage.setItem(key, JSON.stringify(value));&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeManifestTitle(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var match;&lt;br /&gt;
        var i;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        for (i = 0; i &amp;lt; 3; i += 1) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
            } catch (err) { break; }&lt;br /&gt;
        }&lt;br /&gt;
        match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
        if (match) text = match[1];&lt;br /&gt;
        text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^\/+/, &#039;&#039;)&lt;br /&gt;
            .replace(/^index\.php\/?/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^wiki\/?/i, &#039;&#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
        match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/([^?#]+)(?:[?#].*)?$/i);&lt;br /&gt;
        if (match) text = &#039;File:&#039; + match[1];&lt;br /&gt;
        text = text.split(&#039;#&#039;)[0].replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
        text = text.replace(/^(?:파일|Image|이미지)\s*:/i, &#039;File:&#039;);&lt;br /&gt;
        if (/^(?:mediawiki|template|module|manage|file)\s*:/i.test(text)) {&lt;br /&gt;
            text = text.replace(/^([^:]+)\s*:\s*/, function (_, ns) { return ns.charAt(0).toUpperCase() + ns.slice(1).toLowerCase() + &#039;:&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
        if (/^File:/i.test(text)) text = &#039;File:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        if (/^Mediawiki:/i.test(text)) text = &#039;MediaWiki:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        return text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resourceToken(resource) {&lt;br /&gt;
        if (!resource || typeof resource !== &#039;object&#039;) return &#039;&#039;;&lt;br /&gt;
        return String(resource.revision || resource.sha1 || resource.hash || resource.timestamp || resource.updatedAt || resource.url || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createEntryCache() {&lt;br /&gt;
        var index = readLocalJson(ENTRY_CACHE_INDEX_KEY, { entries: {} }) || { entries: {} };&lt;br /&gt;
        var packs = readLocalJson(ENTRY_CACHE_PACK_KEY, { packs: {} }) || { packs: {} };&lt;br /&gt;
        var objectUrls = {};&lt;br /&gt;
        var stats = {&lt;br /&gt;
            textHits: 0,&lt;br /&gt;
            blobHits: 0,&lt;br /&gt;
            misses: 0,&lt;br /&gt;
            networkStores: 0,&lt;br /&gt;
            stores: 0,&lt;br /&gt;
            deletes: 0&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        function ensureIndex() {&lt;br /&gt;
            if (!index || typeof index !== &#039;object&#039;) index = { entries: {} };&lt;br /&gt;
            if (!index.entries || typeof index.entries !== &#039;object&#039;) index.entries = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactIndexForLocalStorage() {&lt;br /&gt;
            var entries;&lt;br /&gt;
            var next = {};&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var entry = entries[key] || {};&lt;br /&gt;
                var kind = String(entry.kind || &#039;raw&#039;).toLowerCase();&lt;br /&gt;
                /*&lt;br /&gt;
                 * CacheStorage already owns image/blob bodies.  Keeping one localStorage&lt;br /&gt;
                 * metadata row per flag/blob competes with MediaWiki ResourceLoader&#039;s own&lt;br /&gt;
                 * localStorage module store and can trigger QuotaExceededError before our&lt;br /&gt;
                 * code even runs on the next page load.  Text/json/file URL rows stay indexed&lt;br /&gt;
                 * because they are small and useful for invalidation diagnostics.&lt;br /&gt;
                 */&lt;br /&gt;
                if (kind === &#039;image&#039; || kind === &#039;blob&#039;) return;&lt;br /&gt;
                next[key] = entry;&lt;br /&gt;
            });&lt;br /&gt;
            index.entries = next;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensurePacks() {&lt;br /&gt;
            if (!packs || typeof packs !== &#039;object&#039;) packs = { packs: {} };&lt;br /&gt;
            if (!packs.packs || typeof packs.packs !== &#039;object&#039;) packs.packs = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function saveIndex() {&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            compactIndexForLocalStorage();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_INDEX_KEY, index);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function savePacks() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_PACK_KEY, packs);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function requestForKey(key) {&lt;br /&gt;
            return new Request(ENTRY_CACHE_REQUEST_PREFIX + encodeURIComponent(String(key || &#039;&#039;)), { credentials: &#039;same-origin&#039; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function openCache() {&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return window.caches.open(ENTRY_CACHE_NAME).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheEntry(key, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            index.entries[key] = Object.assign({}, index.entries[key] || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                resourceKey: String(meta &amp;amp;&amp;amp; meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                token: String(meta &amp;amp;&amp;amp; meta.token || &#039;&#039;),&lt;br /&gt;
                kind: String(meta &amp;amp;&amp;amp; meta.kind || &#039;raw&#039;),&lt;br /&gt;
                contentType: String(meta &amp;amp;&amp;amp; meta.contentType || &#039;&#039;),&lt;br /&gt;
                size: Number(meta &amp;amp;&amp;amp; meta.size || 0) || null,&lt;br /&gt;
                createdAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].createdAt ? index.entries[key].createdAt : now(),&lt;br /&gt;
                updatedAt: now(),&lt;br /&gt;
                lastHitAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].lastHitAt ? index.entries[key].lastHitAt : null,&lt;br /&gt;
                hits: Number(index.entries[key] &amp;amp;&amp;amp; index.entries[key].hits || 0)&lt;br /&gt;
            });&lt;br /&gt;
            saveIndex();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function markHit(key, field) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            if (index.entries[key]) {&lt;br /&gt;
                index.entries[key].hits = Number(index.entries[key].hits || 0) + 1;&lt;br /&gt;
                index.entries[key].lastHitAt = now();&lt;br /&gt;
                saveIndex();&lt;br /&gt;
            }&lt;br /&gt;
            if (field &amp;amp;&amp;amp; stats[field] != null) stats[field] += 1;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getResponse(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return null;&lt;br /&gt;
                return cache.match(requestForKey(key)).then(function (res) {&lt;br /&gt;
                    if (!res) {&lt;br /&gt;
                        stats.misses += 1;&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }&lt;br /&gt;
                    return res;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putResponse(key, response, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !response || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var cloned;&lt;br /&gt;
                var headers;&lt;br /&gt;
                var bodyPromise;&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                cloned = response.clone();&lt;br /&gt;
                bodyPromise = cloned.blob().catch(function () { return null; });&lt;br /&gt;
                return bodyPromise.then(function (blob) {&lt;br /&gt;
                    if (!blob) return false;&lt;br /&gt;
                    headers = new Headers(response.headers || {});&lt;br /&gt;
                    if (!headers.get(&#039;Content-Type&#039;)) headers.set(&#039;Content-Type&#039;, meta.contentType || blob.type || &#039;application/octet-stream&#039;);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cache-Key&#039;, key);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Resource-Key&#039;, String(meta.resourceKey || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Revision-Token&#039;, String(meta.token || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cached-At&#039;, String(now()));&lt;br /&gt;
                    return cache.put(requestForKey(key), new Response(blob, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                        stats.stores += 1;&lt;br /&gt;
                        cacheEntry(key, {&lt;br /&gt;
                            resourceKey: meta.resourceKey,&lt;br /&gt;
                            token: meta.token,&lt;br /&gt;
                            kind: meta.kind || &#039;blob&#039;,&lt;br /&gt;
                            contentType: headers.get(&#039;Content-Type&#039;) || blob.type || &#039;&#039;,&lt;br /&gt;
                            size: blob.size&lt;br /&gt;
                        });&lt;br /&gt;
                        return true;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getText(key) {&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return null;&lt;br /&gt;
                markHit(key, &#039;textHits&#039;);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putText(key, text, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var headers;&lt;br /&gt;
                var body = String(text || &#039;&#039;);&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                headers = new Headers({&lt;br /&gt;
                    &#039;Content-Type&#039;: meta.contentType || &#039;text/plain; charset=UTF-8&#039;,&lt;br /&gt;
                    &#039;X-Entry-Cache-Key&#039;: key,&lt;br /&gt;
                    &#039;X-Entry-Resource-Key&#039;: String(meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Revision-Token&#039;: String(meta.token || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Cached-At&#039;: String(now())&lt;br /&gt;
                });&lt;br /&gt;
                return cache.put(requestForKey(key), new Response(body, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                    stats.stores += 1;&lt;br /&gt;
                    cacheEntry(key, {&lt;br /&gt;
                        resourceKey: meta.resourceKey,&lt;br /&gt;
                        token: meta.token,&lt;br /&gt;
                        kind: meta.kind || &#039;text&#039;,&lt;br /&gt;
                        contentType: headers.get(&#039;Content-Type&#039;),&lt;br /&gt;
                        size: body.length&lt;br /&gt;
                    });&lt;br /&gt;
                    return true;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getBlobUrl(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                return Promise.resolve(objectUrls[key]);&lt;br /&gt;
            }&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return &#039;&#039;;&lt;br /&gt;
                return res.blob().then(function (blob) {&lt;br /&gt;
                    if (!blob || !blob.size) return &#039;&#039;;&lt;br /&gt;
                    objectUrls[key] = URL.createObjectURL(blob);&lt;br /&gt;
                    markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                    return objectUrls[key];&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchBlobUrl(url, key, meta, options) {&lt;br /&gt;
            url = String(url || &#039;&#039;).trim();&lt;br /&gt;
            key = String(key || &#039;&#039;).trim();&lt;br /&gt;
            if (!url || !key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            return getBlobUrl(key).then(function (cachedUrl) {&lt;br /&gt;
                if (cachedUrl) return cachedUrl;&lt;br /&gt;
                return fetch(url, {&lt;br /&gt;
                    credentials: &#039;same-origin&#039;,&lt;br /&gt;
                    cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
                }).then(function (res) {&lt;br /&gt;
                    if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                    stats.networkStores += 1;&lt;br /&gt;
                    return putResponse(key, res, Object.assign({}, meta || {}, { kind: meta &amp;amp;&amp;amp; meta.kind ? meta.kind : &#039;blob&#039; })).then(function () {&lt;br /&gt;
                        return getBlobUrl(key);&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return &#039;&#039;;&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function deleteKey(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
                delete objectUrls[key];&lt;br /&gt;
            }&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                return cache.delete(requestForKey(key)).then(function (ok) {&lt;br /&gt;
                    ensureIndex();&lt;br /&gt;
                    if (index.entries &amp;amp;&amp;amp; index.entries[key]) {&lt;br /&gt;
                        delete index.entries[key];&lt;br /&gt;
                        stats.deletes += 1;&lt;br /&gt;
                        saveIndex();&lt;br /&gt;
                    }&lt;br /&gt;
                    return ok;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function invalidateResources(resourceKeys) {&lt;br /&gt;
            var map = {};&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys = [];&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            (resourceKeys || []).forEach(function (resourceKey) {&lt;br /&gt;
                resourceKey = String(resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (resourceKey) map[resourceKey] = true;&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(entries).forEach(function (cacheKey) {&lt;br /&gt;
                var resourceKey = String(entries[cacheKey] &amp;amp;&amp;amp; entries[cacheKey].resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (map[resourceKey]) keys.push(cacheKey);&lt;br /&gt;
            });&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function prune(maxAgeDays) {&lt;br /&gt;
            var days = Math.max(1, Number(maxAgeDays) || 7);&lt;br /&gt;
            var cutoff = now() - days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys;&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            keys = Object.keys(entries).filter(function (key) {&lt;br /&gt;
                return Number(entries[key] &amp;amp;&amp;amp; entries[key].createdAt || 0) &amp;lt; cutoff;&lt;br /&gt;
            });&lt;br /&gt;
            window.localStorage &amp;amp;&amp;amp; window.localStorage.setItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY, String(now()));&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function reset() {&lt;br /&gt;
            Object.keys(objectUrls).forEach(function (key) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
            });&lt;br /&gt;
            objectUrls = {};&lt;br /&gt;
            index = { entries: {} };&lt;br /&gt;
            packs = { packs: {} };&lt;br /&gt;
            saveIndex();&lt;br /&gt;
            savePacks();&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            return window.caches.delete(ENTRY_CACHE_NAME).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packReady(key, token) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            return !!(packs.packs[key] &amp;amp;&amp;amp; String(packs.packs[key].token || &#039;&#039;) === token &amp;amp;&amp;amp; packs.packs[key].ready);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setPackReady(key, token, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            packs.packs[key] = Object.assign({}, meta || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                token: token,&lt;br /&gt;
                ready: true,&lt;br /&gt;
                updatedAt: now()&lt;br /&gt;
            });&lt;br /&gt;
            savePacks();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packInfo() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            return Object.assign({}, packs.packs || {});&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function info() {&lt;br /&gt;
            var lastClientPrune = 0;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var byKind = {};&lt;br /&gt;
            try { lastClientPrune = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var kind = String(entries[key] &amp;amp;&amp;amp; entries[key].kind || &#039;raw&#039;);&lt;br /&gt;
                byKind[kind] = (byKind[kind] || 0) + 1;&lt;br /&gt;
            });&lt;br /&gt;
            return {&lt;br /&gt;
                supported: hasCacheStorage(),&lt;br /&gt;
                name: ENTRY_CACHE_NAME,&lt;br /&gt;
                entries: Object.keys(entries).length,&lt;br /&gt;
                byKind: byKind,&lt;br /&gt;
                packs: Object.keys(packs.packs || {}).length,&lt;br /&gt;
                lastClientPruneAt: lastClientPrune || null,&lt;br /&gt;
                stats: Object.assign({}, stats)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Trim legacy image/blob metadata rows produced by older builds. */&lt;br /&gt;
        try { saveIndex(); } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            getResponse: getResponse,&lt;br /&gt;
            putResponse: putResponse,&lt;br /&gt;
            getText: getText,&lt;br /&gt;
            putText: putText,&lt;br /&gt;
            getBlobUrl: getBlobUrl,&lt;br /&gt;
            fetchBlobUrl: fetchBlobUrl,&lt;br /&gt;
            invalidateResources: invalidateResources,&lt;br /&gt;
            prune: prune,&lt;br /&gt;
            reset: reset,&lt;br /&gt;
            packReady: packReady,&lt;br /&gt;
            setPackReady: setPackReady,&lt;br /&gt;
            packInfo: packInfo,&lt;br /&gt;
            info: info&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryCache = window.EntryCache || createEntryCache();&lt;br /&gt;
&lt;br /&gt;
    function createRevisionManifestService() {&lt;br /&gt;
        var current = null;&lt;br /&gt;
        var previous = readLocalJson(REVISION_MANIFEST_LOCAL_KEY, null);&lt;br /&gt;
        var loadPromise = null;&lt;br /&gt;
        var changedResources = [];&lt;br /&gt;
&lt;br /&gt;
        function unwrap(payload) {&lt;br /&gt;
            return payload &amp;amp;&amp;amp; (payload.entryrevisionmanifest || payload.revisionManifest || payload) || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourcesOf(manifest) {&lt;br /&gt;
            return manifest &amp;amp;&amp;amp; manifest.resources &amp;amp;&amp;amp; typeof manifest.resources === &#039;object&#039; ? manifest.resources : {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactRevisionManifest(manifest) {&lt;br /&gt;
            var out;&lt;br /&gt;
            var resources;&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039;) return null;&lt;br /&gt;
            out = {&lt;br /&gt;
                manifestVersion: manifest.manifestVersion || manifest.version || &#039;&#039;,&lt;br /&gt;
                version: manifest.version || manifest.manifestVersion || &#039;&#039;,&lt;br /&gt;
                generatedAt: manifest.generatedAt || &#039;&#039;,&lt;br /&gt;
                pruneDays: manifest.pruneDays,&lt;br /&gt;
                clientPruneDays: manifest.clientPruneDays,&lt;br /&gt;
                resources: {}&lt;br /&gt;
            };&lt;br /&gt;
            resources = resourcesOf(manifest);&lt;br /&gt;
            Object.keys(resources).forEach(function (title) {&lt;br /&gt;
                var src = resources[title];&lt;br /&gt;
                var dst;&lt;br /&gt;
                if (!src || typeof src !== &#039;object&#039;) return;&lt;br /&gt;
                dst = {};&lt;br /&gt;
                [&#039;revision&#039;, &#039;sha1&#039;, &#039;hash&#039;, &#039;timestamp&#039;, &#039;updatedAt&#039;, &#039;url&#039;].forEach(function (key) {&lt;br /&gt;
                    if (src[key] !== undefined &amp;amp;&amp;amp; src[key] !== null &amp;amp;&amp;amp; String(src[key]) !== &#039;&#039;) dst[key] = src[key];&lt;br /&gt;
                });&lt;br /&gt;
                out.resources[title] = dst;&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function buildLookup(manifest) {&lt;br /&gt;
            var lookup = {};&lt;br /&gt;
            Object.keys(resourcesOf(manifest)).forEach(function (title) {&lt;br /&gt;
                lookup[normalizeManifestTitle(title).toLowerCase()] = resourcesOf(manifest)[title];&lt;br /&gt;
            });&lt;br /&gt;
            return lookup;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function computeChanged(prev, next) {&lt;br /&gt;
            var prevLookup = buildLookup(prev);&lt;br /&gt;
            var nextLookup = buildLookup(next);&lt;br /&gt;
            var out = [];&lt;br /&gt;
            Object.keys(nextLookup).forEach(function (key) {&lt;br /&gt;
                if (resourceToken(prevLookup[key]) !== resourceToken(nextLookup[key])) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(prevLookup).forEach(function (key) {&lt;br /&gt;
                if (!nextLookup[key]) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            return unique(out);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function load(options) {&lt;br /&gt;
            if (loadPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return loadPromise;&lt;br /&gt;
            loadPromise = fetchApi({&lt;br /&gt;
                action: REVISION_MANIFEST_ACTION,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;&lt;br /&gt;
            }).then(function (payload) {&lt;br /&gt;
                var manifest = unwrap(payload);&lt;br /&gt;
                if (!manifest || !manifest.resources) throw new Error(&#039;invalid revision manifest&#039;);&lt;br /&gt;
                current = compactRevisionManifest(manifest) || manifest;&lt;br /&gt;
                changedResources = computeChanged(previous, current);&lt;br /&gt;
                if (changedResources.length &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.invalidateResources === &#039;function&#039;) {&lt;br /&gt;
                    window.EntryCache.invalidateResources(changedResources);&lt;br /&gt;
                }&lt;br /&gt;
                writeLocalJson(REVISION_MANIFEST_LOCAL_KEY, current);&lt;br /&gt;
                previous = current;&lt;br /&gt;
                maybePrune();&lt;br /&gt;
                return current;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                current = previous || null;&lt;br /&gt;
                return current;&lt;br /&gt;
            });&lt;br /&gt;
            return loadPromise;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensureLoaded() {&lt;br /&gt;
            return current ? Promise.resolve(current) : load();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            var lookup;&lt;br /&gt;
            if (!title || !current) return null;&lt;br /&gt;
            lookup = buildLookup(current);&lt;br /&gt;
            return lookup[title.toLowerCase()] || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenForRef(ref) {&lt;br /&gt;
            return resourceToken(resourceForRef(ref));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceKeyForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            return title ? title.toLowerCase() : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForRef(ref, type) {&lt;br /&gt;
            var resourceKey = resourceKeyForRef(ref);&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            if (!resourceKey || !token) return &#039;&#039;;&lt;br /&gt;
            return String(type || &#039;raw&#039;) + &#039;:&#039; + resourceKey + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function manifestToken(extra) {&lt;br /&gt;
            var base = current &amp;amp;&amp;amp; (current.manifestVersion || current.version || current.generatedAt || &#039;&#039;) || &#039;&#039;;&lt;br /&gt;
            return String(base || &#039;no-manifest&#039;) + (extra ? (&#039;:&#039; + String(extra)) : &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenFromUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;);&lt;br /&gt;
            var match = text.match(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=([^&amp;amp;]+)/);&lt;br /&gt;
            if (!match) return &#039;&#039;;&lt;br /&gt;
            try { return decodeURIComponent(match[1]); } catch (err) { return match[1]; }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForUrl(url, type) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var token = tokenFromUrl(text) || manifestToken(&#039;url&#039;);&lt;br /&gt;
            var normalized;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                normalized = new URL(text, window.location.href);&lt;br /&gt;
                text = normalized.pathname + (normalized.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            text = text.replace(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=[^&amp;amp;]*/g, &#039;&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            try { text = decodeURI(text); } catch (err2) {}&lt;br /&gt;
            return String(type || &#039;blob&#039;) + &#039;:url:&#039; + text.toLowerCase() + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function addRevisionParam(url, ref) {&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var sep;&lt;br /&gt;
            if (!text || !token || /[?&amp;amp;]_entryRev=/.test(text)) return text;&lt;br /&gt;
            sep = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + sep + &#039;_entryRev=&#039; + encodeURIComponent(token);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function maybePrune() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var due;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            due = !last || (now() - last &amp;gt;= days * 24 * 60 * 60 * 1000);&lt;br /&gt;
            if (due &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.prune === &#039;function&#039;) {&lt;br /&gt;
                window.EntryCache.prune(days);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function countdown() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var next;&lt;br /&gt;
            var remain;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            if (!last) return { days: days, lastClientPruneAt: null, nextClientPruneAt: null, remainingMs: 0, remainingDays: 0, due: true };&lt;br /&gt;
            next = last + days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            remain = Math.max(0, next - now());&lt;br /&gt;
            return {&lt;br /&gt;
                days: days,&lt;br /&gt;
                lastClientPruneAt: last,&lt;br /&gt;
                nextClientPruneAt: next,&lt;br /&gt;
                remainingMs: remain,&lt;br /&gt;
                remainingDays: Math.ceil(remain / (24 * 60 * 60 * 1000)),&lt;br /&gt;
                due: remain &amp;lt;= 0&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function status() {&lt;br /&gt;
            return {&lt;br /&gt;
                available: !!current,&lt;br /&gt;
                manifestVersion: current &amp;amp;&amp;amp; (current.manifestVersion || current.version || &#039;&#039;),&lt;br /&gt;
                generatedAt: current &amp;amp;&amp;amp; current.generatedAt || &#039;&#039;,&lt;br /&gt;
                changedResources: changedResources.slice(0, 50),&lt;br /&gt;
                changedCount: changedResources.length,&lt;br /&gt;
                prune: countdown(),&lt;br /&gt;
                cache: window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.info === &#039;function&#039; ? window.EntryCache.info() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            load: load,&lt;br /&gt;
            ensureLoaded: ensureLoaded,&lt;br /&gt;
            current: function () { return current; },&lt;br /&gt;
            resourceForRef: resourceForRef,&lt;br /&gt;
            tokenForRef: tokenForRef,&lt;br /&gt;
            cacheKeyForRef: cacheKeyForRef,&lt;br /&gt;
            cacheKeyForUrl: cacheKeyForUrl,&lt;br /&gt;
            manifestToken: manifestToken,&lt;br /&gt;
            resourceKeyForRef: resourceKeyForRef,&lt;br /&gt;
            addRevisionParam: addRevisionParam,&lt;br /&gt;
            maybePrune: maybePrune,&lt;br /&gt;
            countdown: countdown,&lt;br /&gt;
            status: status&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.RevisionManifest = window.RevisionManifest || createRevisionManifestService();&lt;br /&gt;
&lt;br /&gt;
    function createEntryStore() {&lt;br /&gt;
        var jsonCache = {};&lt;br /&gt;
        var flagUrlCache = {};&lt;br /&gt;
        var fileUrlCache = {};&lt;br /&gt;
        var imageReadyCache = {};&lt;br /&gt;
        var imageObjectCache = {};&lt;br /&gt;
        var imageDisplayUrlCache = {};&lt;br /&gt;
        var imagePromiseCache = {};&lt;br /&gt;
        var rawPromiseCache = {};&lt;br /&gt;
        var textCache = {};&lt;br /&gt;
        var textPromiseCache = {};&lt;br /&gt;
&lt;br /&gt;
        function fetchJsonRef(ref, options) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var promiseKey;&lt;br /&gt;
            var url;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty json ref&#039;));&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(ref, &#039;json&#039;) : &#039;&#039;;&lt;br /&gt;
            promiseKey = cacheKey ? (key + &#039;@&#039; + cacheKey) : key;&lt;br /&gt;
            if (jsonCache[promiseKey]) return Promise.resolve(jsonCache[promiseKey].data);&lt;br /&gt;
            if (rawPromiseCache[promiseKey]) return rawPromiseCache[promiseKey];&lt;br /&gt;
            url = rawUrlForRef(ref, &#039;application/json&#039;);&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
            }&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(ref) : key;&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(ref) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                rawPromiseCache[promiseKey] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        var cachedData = cachedText &amp;amp;&amp;amp; cachedText.trim() ? JSON.parse(cachedText) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: cachedData, loadedAt: now(), persistent: true };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        return cachedData;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token });&lt;br /&gt;
                        return data;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return rawPromiseCache[promiseKey];&lt;br /&gt;
            }&lt;br /&gt;
            rawPromiseCache[promiseKey] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                return data;&lt;br /&gt;
            });&lt;br /&gt;
            return rawPromiseCache[promiseKey];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getJsonSync(ref) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            return key &amp;amp;&amp;amp; jsonCache[key] ? jsonCache[key].data : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setJsonRef(ref, data) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            jsonCache[key] = { key: key, ref: ref, url: rawUrlForRef(ref, &#039;application/json&#039;), data: data, loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeFile(value) {&lt;br /&gt;
            return String(value || &#039;&#039;)&lt;br /&gt;
                .replace(/^(?:file|파일):/i, &#039;&#039;)&lt;br /&gt;
                .trim();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileKey(value) {&lt;br /&gt;
            return normalizeFile(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setFlagUrl(file, url) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            flagUrlCache[key] = String(url || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getFlagUrl(file) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            return key ? (flagUrlCache[key] || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFlagUrls(files) {&lt;br /&gt;
            var clean = unique((files || []).map(normalizeFile).filter(Boolean));&lt;br /&gt;
&lt;br /&gt;
            function cacheKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;flagurl&#039;) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function resourceKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + fileKey(file));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function tokenForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function hydrateCachedFlagUrl(file) {&lt;br /&gt;
                var key = fileKey(file);&lt;br /&gt;
                var cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                if (!key || flagUrlCache[key] !== undefined || !cacheKey || !window.EntryCache || typeof window.EntryCache.getText !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.resolve(false);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        flagUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return true;&lt;br /&gt;
                    }&lt;br /&gt;
                    return false;&lt;br /&gt;
                }).catch(function () { return false; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(clean.map(hydrateCachedFlagUrl)).then(function () {&lt;br /&gt;
                var pending = clean.filter(function (file) {&lt;br /&gt;
                    return getFlagUrl(file) === &#039;&#039; &amp;amp;&amp;amp; flagUrlCache[fileKey(file)] === undefined;&lt;br /&gt;
                });&lt;br /&gt;
                var chunks = [];&lt;br /&gt;
&lt;br /&gt;
                while (pending.length) chunks.push(pending.splice(0, 20));&lt;br /&gt;
                if (!chunks.length) return flagUrlCache;&lt;br /&gt;
&lt;br /&gt;
                return Promise.all(chunks.map(function (chunk) {&lt;br /&gt;
                    var titleToKey = {};&lt;br /&gt;
                    var keyToFile = {};&lt;br /&gt;
                    var titles = [];&lt;br /&gt;
                    chunk.forEach(function (file) {&lt;br /&gt;
                        var key = fileKey(file);&lt;br /&gt;
                        if (!key) return;&lt;br /&gt;
                        titleToKey[fileKey(&#039;File:&#039; + file)] = key;&lt;br /&gt;
                        titleToKey[fileKey(&#039;파일:&#039; + file)] = key;&lt;br /&gt;
                        keyToFile[key] = file;&lt;br /&gt;
                        titles.push(&#039;File:&#039; + file);&lt;br /&gt;
                        titles.push(&#039;파일:&#039; + file);&lt;br /&gt;
                    });&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size&#039;,&lt;br /&gt;
                        iiurlwidth: &#039;16&#039;,&lt;br /&gt;
                        titles: titles.join(&#039;|&#039;)&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var seen = {};&lt;br /&gt;
                        pages.forEach(function (page) {&lt;br /&gt;
                            var key = titleToKey[fileKey(page &amp;amp;&amp;amp; page.title)] || fileKey(page &amp;amp;&amp;amp; page.title);&lt;br /&gt;
                            var file = keyToFile[key] || (page &amp;amp;&amp;amp; page.title || &#039;&#039;).replace(/^(?:File|파일):/i, &#039;&#039;);&lt;br /&gt;
                            var imageinfo = page &amp;amp;&amp;amp; page.imageinfo &amp;amp;&amp;amp; page.imageinfo[0];&lt;br /&gt;
                            var url;&lt;br /&gt;
                            var rev;&lt;br /&gt;
                            var sep;&lt;br /&gt;
                            var cacheKey;&lt;br /&gt;
                            if (!key) return;&lt;br /&gt;
                            seen[key] = true;&lt;br /&gt;
                            url = imageinfo &amp;amp;&amp;amp; (imageinfo.thumburl || imageinfo.url) ? (imageinfo.thumburl || imageinfo.url) : &#039;&#039;;&lt;br /&gt;
                            if (url) {&lt;br /&gt;
                                rev = imageinfo &amp;amp;&amp;amp; (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) ? (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) : &#039;&#039;;&lt;br /&gt;
                                if (rev &amp;amp;&amp;amp; !/[?&amp;amp;]_entryFileRev=/.test(url)) {&lt;br /&gt;
                                    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                                    url += sep + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                                }&lt;br /&gt;
                            }&lt;br /&gt;
                            flagUrlCache[key] = url;&lt;br /&gt;
                            cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.putText === &#039;function&#039;) {&lt;br /&gt;
                                window.EntryCache.putText(cacheKey, url || &#039;&#039;, {&lt;br /&gt;
                                    resourceKey: resourceKeyForFile(file),&lt;br /&gt;
                                    token: tokenForFile(file),&lt;br /&gt;
                                    kind: &#039;flagurl&#039;,&lt;br /&gt;
                                    contentType: &#039;text/plain; charset=UTF-8&#039;&lt;br /&gt;
                                });&lt;br /&gt;
                            }&lt;br /&gt;
                        });&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; !seen[key] &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () { return flagUrlCache; });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeGenericFileTitle(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            var match;&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            for (i = 0; i &amp;lt; 4; i += 1) {&lt;br /&gt;
                try {&lt;br /&gt;
                    if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;).replace(/^\/+/, &#039;&#039;).replace(/^index\.php\/?/i, &#039;&#039;).replace(/^wiki\/?/i, &#039;&#039;).trim();&lt;br /&gt;
            match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/(.+)$/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^(?:File|파일|Image|이미지)\s*:/i, &#039;&#039;).replace(/^:+/, &#039;&#039;).trim();&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isFileRef(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            return /^(?:file|파일)\s*:/i.test(text) || /(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\//i.test(text);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileUrlKey(value) {&lt;br /&gt;
            return normalizeGenericFileTitle(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function stableDirectImageUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var separator;&lt;br /&gt;
            var bust;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            if (/[?&amp;amp;]_entryAsset=/.test(text) || /[?&amp;amp;]_=/.test(text)) return text;&lt;br /&gt;
            bust = String(BUILD_ID || &#039;entry&#039;);&lt;br /&gt;
            separator = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + separator + &#039;_entryAsset=&#039; + encodeURIComponent(bust);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFileUrl(ref) {&lt;br /&gt;
            var original = String(ref || &#039;&#039;).trim();&lt;br /&gt;
            var file;&lt;br /&gt;
            var key;&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!original) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (!isFileRef(original)) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            file = normalizeGenericFileTitle(original);&lt;br /&gt;
            key = fileUrlKey(file);&lt;br /&gt;
            if (!file || !key) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            if (fileUrlCache[key]) return Promise.resolve(fileUrlCache[key]);&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;fileurl&#039;) : &#039;&#039;;&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + key);&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        fileUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return fileUrlCache[key];&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                        titles: &#039;File:&#039; + file&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                        var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                        var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                        var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                        var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                        fileUrlCache[key] = resolved;&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, resolved, { resourceKey: resourceKey, token: token, kind: &#039;fileurl&#039;, contentType: &#039;text/plain; charset=UTF-8&#039; });&lt;br /&gt;
                        return resolved;&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return stableDirectImageUrl(original);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
            return fetchApi({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;,&lt;br /&gt;
                redirects: &#039;1&#039;,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + file&lt;br /&gt;
            }).then(function (json) {&lt;br /&gt;
                var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                fileUrlCache[key] = resolved;&lt;br /&gt;
                return resolved;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                return stableDirectImageUrl(original);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImageUrl(url, options) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var sourcePromise;&lt;br /&gt;
            var persistent = !(options &amp;amp;&amp;amp; options.persistent === false);&lt;br /&gt;
            if (!key) return Promise.resolve(false);&lt;br /&gt;
            if (imageReadyCache[key]) return Promise.resolve(true);&lt;br /&gt;
            if (imagePromiseCache[key]) return imagePromiseCache[key];&lt;br /&gt;
&lt;br /&gt;
            cacheKey = persistent &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039; ? window.RevisionManifest.cacheKeyForUrl(key, &#039;image&#039;) : &#039;&#039;;&lt;br /&gt;
            sourcePromise = Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.fetchBlobUrl === &#039;function&#039;) {&lt;br /&gt;
                sourcePromise = window.EntryCache.fetchBlobUrl(key, cacheKey, {&lt;br /&gt;
                    resourceKey: &#039;&#039;,&lt;br /&gt;
                    token: &#039;&#039;,&lt;br /&gt;
                    kind: &#039;image&#039;&lt;br /&gt;
                }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            imagePromiseCache[key] = sourcePromise.then(function (cachedObjectUrl) {&lt;br /&gt;
                return new Promise(function (resolve) {&lt;br /&gt;
                    var img = new Image();&lt;br /&gt;
                    var settled = false;&lt;br /&gt;
                    var src = cachedObjectUrl || key;&lt;br /&gt;
                    function finish(ok) {&lt;br /&gt;
                        if (settled) return;&lt;br /&gt;
                        settled = true;&lt;br /&gt;
                        if (ok) {&lt;br /&gt;
                            imageReadyCache[key] = true;&lt;br /&gt;
                            imageReadyCache[src] = true;&lt;br /&gt;
                            imageObjectCache[key] = img;&lt;br /&gt;
                            imageObjectCache[src] = img;&lt;br /&gt;
                            imageDisplayUrlCache[key] = src;&lt;br /&gt;
                            imageDisplayUrlCache[src] = src;&lt;br /&gt;
                        }&lt;br /&gt;
                        resolve(!!ok);&lt;br /&gt;
                    }&lt;br /&gt;
                    img.onload = function () {&lt;br /&gt;
                        if (img.decode) {&lt;br /&gt;
                            img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        } else {&lt;br /&gt;
                            finish(true);&lt;br /&gt;
                        }&lt;br /&gt;
                    };&lt;br /&gt;
                    img.onerror = function () {&lt;br /&gt;
                        if (src !== key) {&lt;br /&gt;
                            src = key;&lt;br /&gt;
                            img.src = key;&lt;br /&gt;
                            return;&lt;br /&gt;
                        }&lt;br /&gt;
                        finish(false);&lt;br /&gt;
                    };&lt;br /&gt;
                    img.decoding = &#039;async&#039;;&lt;br /&gt;
                    img.loading = &#039;eager&#039;;&lt;br /&gt;
                    img.src = src;&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        if (img.decode) img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        else finish(true);&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
            return imagePromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImages(urls, options) {&lt;br /&gt;
            var list = unique((urls || []).filter(Boolean));&lt;br /&gt;
            var limit = options &amp;amp;&amp;amp; Number(options.limit);&lt;br /&gt;
            var concurrency = Math.max(1, Math.min(48, Number(options &amp;amp;&amp;amp; options.concurrency) || 24));&lt;br /&gt;
            var index = 0;&lt;br /&gt;
            var ok = 0;&lt;br /&gt;
            var fail = 0;&lt;br /&gt;
            if (Number.isFinite(limit) &amp;amp;&amp;amp; limit &amp;gt; 0) list = list.slice(0, limit);&lt;br /&gt;
            if (!list.length) return Promise.resolve({ total: 0, ok: 0, fail: 0 });&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                function pump() {&lt;br /&gt;
                    while (index &amp;lt; list.length &amp;amp;&amp;amp; concurrency &amp;gt; 0) {&lt;br /&gt;
                        (function (url) {&lt;br /&gt;
                            concurrency -= 1;&lt;br /&gt;
                            preloadImageUrl(url, options || {}).then(function (result) {&lt;br /&gt;
                                if (result) ok += 1;&lt;br /&gt;
                                else fail += 1;&lt;br /&gt;
                            }).catch(function () {&lt;br /&gt;
                                fail += 1;&lt;br /&gt;
                            }).then(function () {&lt;br /&gt;
                                concurrency += 1;&lt;br /&gt;
                                if (index &amp;gt;= list.length &amp;amp;&amp;amp; ok + fail &amp;gt;= list.length) resolve({ total: list.length, ok: ok, fail: fail });&lt;br /&gt;
                                else pump();&lt;br /&gt;
                            });&lt;br /&gt;
                        })(list[index++]);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                pump();&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isImageReady(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return !!(key &amp;amp;&amp;amp; imageReadyCache[key]);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageElement(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageObjectCache[key] || null) : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageDisplayUrl(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageDisplayUrlCache[key] || key) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeUrlKey(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var a;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                a = document.createElement(&#039;a&#039;);&lt;br /&gt;
                a.href = text;&lt;br /&gt;
                text = a.pathname + (a.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            try {&lt;br /&gt;
                text = decodeURI(text);&lt;br /&gt;
            } catch (err2) {}&lt;br /&gt;
            text = text.replace(/([?&amp;amp;])_=[^&amp;amp;]*/g, &#039;$1&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            text = text.replace(/_/g, &#039;_&#039;);&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchTextUrl(url, options) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceRef;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty text url&#039;));&lt;br /&gt;
            if (textCache[key]) return Promise.resolve(textCache[key].text);&lt;br /&gt;
            if (textPromiseCache[key]) return textPromiseCache[key];&lt;br /&gt;
            resourceRef = options &amp;amp;&amp;amp; options.resourceRef ? options.resourceRef : (options &amp;amp;&amp;amp; options.ref ? options.ref : &#039;&#039;);&lt;br /&gt;
            cacheKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(resourceRef, &#039;text&#039;) : &#039;&#039;;&lt;br /&gt;
            if (!cacheKey &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039;) cacheKey = window.RevisionManifest.cacheKeyForUrl(url, &#039;text&#039;);&lt;br /&gt;
            resourceKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(resourceRef) : (&#039;url:&#039; + key);&lt;br /&gt;
            token = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(resourceRef) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                textPromiseCache[key] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: cachedText, loadedAt: now(), persistent: true };&lt;br /&gt;
                        return cachedText;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token, kind: &#039;text&#039;, contentType: &#039;text/html; charset=UTF-8&#039; });&lt;br /&gt;
                        return text;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return textPromiseCache[key];&lt;br /&gt;
            }&lt;br /&gt;
            textPromiseCache[key] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                return text;&lt;br /&gt;
            });&lt;br /&gt;
            return textPromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getTextSync(url) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            return key &amp;amp;&amp;amp; textCache[key] ? textCache[key].text : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setTextUrl(url, text) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            textCache[key] = { key: key, url: url, text: String(text || &#039;&#039;), loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheInfo() {&lt;br /&gt;
            return {&lt;br /&gt;
                json: Object.keys(jsonCache).length,&lt;br /&gt;
                jsonKeys: Object.keys(jsonCache),&lt;br /&gt;
                flags: Object.keys(flagUrlCache).length,&lt;br /&gt;
                files: Object.keys(fileUrlCache).length,&lt;br /&gt;
                images: Object.keys(imageReadyCache).length,&lt;br /&gt;
                retainedImages: Object.keys(imageObjectCache).length,&lt;br /&gt;
                displayImages: Object.keys(imageDisplayUrlCache).length,&lt;br /&gt;
                text: Object.keys(textCache).length,&lt;br /&gt;
                textKeys: Object.keys(textCache)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            fetchJsonRef: fetchJsonRef,&lt;br /&gt;
            getJsonSync: getJsonSync,&lt;br /&gt;
            setJsonRef: setJsonRef,&lt;br /&gt;
            normalizeRefKey: normalizeRefKey,&lt;br /&gt;
            rawUrlForRef: rawUrlForRef,&lt;br /&gt;
            revisionUrlForRef: function (ref, ctype) {&lt;br /&gt;
                var url = rawUrlForRef(ref, ctype);&lt;br /&gt;
                if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                    url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
                }&lt;br /&gt;
                return url;&lt;br /&gt;
            },&lt;br /&gt;
            resolveFlagUrls: resolveFlagUrls,&lt;br /&gt;
            setFlagUrl: setFlagUrl,&lt;br /&gt;
            getFlagUrl: getFlagUrl,&lt;br /&gt;
            resolveFileUrl: resolveFileUrl,&lt;br /&gt;
            preloadImageUrl: preloadImageUrl,&lt;br /&gt;
            preloadImages: preloadImages,&lt;br /&gt;
            isImageReady: isImageReady,&lt;br /&gt;
            getImageElement: getImageElement,&lt;br /&gt;
            getImageDisplayUrl: getImageDisplayUrl,&lt;br /&gt;
            fetchTextUrl: fetchTextUrl,&lt;br /&gt;
            getTextSync: getTextSync,&lt;br /&gt;
            setTextUrl: setTextUrl,&lt;br /&gt;
            stableDirectImageUrl: stableDirectImageUrl,&lt;br /&gt;
            cacheInfo: cacheInfo&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryStore = window.EntryStore || createEntryStore();&lt;br /&gt;
&lt;br /&gt;
    function updateBootProgress(done, total, label) {&lt;br /&gt;
        var pct = total ? Math.max(0, Math.min(100, Math.round((done / total) * 100))) : 0;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootDetailNode &amp;amp;&amp;amp; label) bootDetailNode.textContent = label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function adoptBootScreen(node) {&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        bootNode = node;&lt;br /&gt;
        bootStatusNode = bootNode.querySelector(&#039;.boot-gate-status&#039;);&lt;br /&gt;
        bootProgressNode = bootNode.querySelector(&#039;.boot-gate-progress&#039;);&lt;br /&gt;
        bootDetailNode = bootNode.querySelector(&#039;.boot-gate-detail&#039;);&lt;br /&gt;
        bootFillNode = bootNode.querySelector(&#039;.boot-gate-meter-fill&#039;);&lt;br /&gt;
        bootNode.classList.add(&#039;is-active&#039;);&lt;br /&gt;
        bootNode.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBootScreen(options) {&lt;br /&gt;
        var panel;&lt;br /&gt;
        var header;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var existing;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
        var previewMode;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        previewMode = !!options.preview;&lt;br /&gt;
        if (bootNode &amp;amp;&amp;amp; bootNode.parentNode) return bootNode;&lt;br /&gt;
        if (!document.body) return null;&lt;br /&gt;
&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !previewMode) return null;&lt;br /&gt;
        if (!previewMode) activateBootSurface();&lt;br /&gt;
&lt;br /&gt;
        existing = document.getElementById(&#039;boot-gate-screen&#039;) || (!previewMode &amp;amp;&amp;amp; window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.ensure ? window.__BootGatePrelude.ensure() : null);&lt;br /&gt;
        if (existing) return adoptBootScreen(existing);&lt;br /&gt;
&lt;br /&gt;
        bootNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootNode.id = &#039;boot-gate-screen&#039;;&lt;br /&gt;
        bootNode.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        bootNode.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        bootNode.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        header = document.createElement(&#039;div&#039;);&lt;br /&gt;
        header.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        header.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootStatusNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootStatusNode.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        bootStatusNode.textContent = &#039;Preparing entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        bootFillNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootFillNode.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(bootFillNode);&lt;br /&gt;
&lt;br /&gt;
        bootProgressNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootProgressNode.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        bootProgressNode.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootDetailNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootDetailNode.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        bootDetailNode.textContent = &#039;loading manifest&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { hideBootScreen({ force: true }); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(header);&lt;br /&gt;
        panel.appendChild(bootStatusNode);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(bootProgressNode);&lt;br /&gt;
        panel.appendChild(bootDetailNode);&lt;br /&gt;
        bootNode.appendChild(decoLayer);&lt;br /&gt;
        bootNode.appendChild(panel);&lt;br /&gt;
        bootNode.appendChild(close);&lt;br /&gt;
        document.body.appendChild(bootNode);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildLoginUrl() {&lt;br /&gt;
        var pageName = String(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        var params = {};&lt;br /&gt;
&lt;br /&gt;
        if (pageName &amp;amp;&amp;amp; !isAuthenticationPage() &amp;amp;&amp;amp; !isCreateAccountPage()) params.returnto = pageName;&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                return mw.util.getUrl(&#039;Special:UserLogin&#039;, params);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return &#039;/index.php?title=Special%3AUserLogin&#039; + (params.returnto ? &#039;&amp;amp;returnto=&#039; + encodeURIComponent(params.returnto) : &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureLoginGateAction(panel) {&lt;br /&gt;
        var copy;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (loginGateActionNode &amp;amp;&amp;amp; loginGateActionNode.parentNode) return loginGateActionNode;&lt;br /&gt;
        loginGateActionNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        loginGateActionNode.className = &#039;boot-gate-login&#039;;&lt;br /&gt;
&lt;br /&gt;
        copy = document.createElement(&#039;div&#039;);&lt;br /&gt;
        copy.className = &#039;boot-gate-login-copy&#039;;&lt;br /&gt;
        copy.textContent = &#039;SIGN IN TO ENTER THE WIKI.&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = document.createElement(&#039;a&#039;);&lt;br /&gt;
        action.className = &#039;boot-gate-login-action&#039;;&lt;br /&gt;
        action.href = buildLoginUrl();&lt;br /&gt;
        action.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        action.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
&lt;br /&gt;
        loginGateActionNode.appendChild(copy);&lt;br /&gt;
        loginGateActionNode.appendChild(action);&lt;br /&gt;
        panel.appendChild(loginGateActionNode);&lt;br /&gt;
        return loginGateActionNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showLoginGate() {&lt;br /&gt;
        var node = ensureBootScreen();&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (!node || !requiresLoginGate()) return false;&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        loginGateLocked = true;&lt;br /&gt;
        node.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.add(&#039;is-active&#039;, &#039;is-login-required&#039;);&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;dialog&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-modal&#039;, &#039;true&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;off&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = node.querySelector(&#039;.boot-gate-panel&#039;);&lt;br /&gt;
        title = node.querySelector(&#039;.boot-gate-title&#039;);&lt;br /&gt;
        if (title) title.textContent = &#039;ACCOUNT AUTHENTICATION&#039;;&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = &#039;LOGIN REQUIRED&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = &#039;100%&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = panel ? ensureLoginGateAction(panel).querySelector(&#039;.boot-gate-login-action&#039;) : null;&lt;br /&gt;
        if (action) {&lt;br /&gt;
            action.href = buildLoginUrl();&lt;br /&gt;
            window.requestAnimationFrame(function () {&lt;br /&gt;
                try { action.focus({ preventScroll: true }); }&lt;br /&gt;
                catch (err) { try { action.focus(); } catch (ignore) {} }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hideBootScreen(options) {&lt;br /&gt;
        var node = bootNode || document.getElementById(&#039;boot-gate-screen&#039;);&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        if (loginGateLocked &amp;amp;&amp;amp; requiresLoginGate() &amp;amp;&amp;amp; !options.force) return false;&lt;br /&gt;
        if (!node) {&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            if (bootNode === node) bootNode = null;&lt;br /&gt;
            loginGateActionNode = null;&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 240);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromNationPayload(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            if (typeof value === &#039;string&#039;) {&lt;br /&gt;
                files.push(value);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof value === &#039;object&#039;) files.push(value.file || value.flag_file || value.flag || value.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function scanItem(item) {&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            if (Array.isArray(item.flags)) item.flags.forEach(add);&lt;br /&gt;
            add(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        (payload &amp;amp;&amp;amp; payload.continents || []).forEach(function (continent) {&lt;br /&gt;
            (continent.regions || []).forEach(function (region) {&lt;br /&gt;
                (region.items || []).forEach(scanItem);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromLinkMap(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        var source = payload &amp;amp;&amp;amp; payload.items ? payload.items : {};&lt;br /&gt;
        Object.keys(source || {}).forEach(function (key) {&lt;br /&gt;
            var item = source[key];&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            files.push(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prewarmImages(urls, limit) {&lt;br /&gt;
        return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
            limit: Number(limit) &amp;gt; 0 ? Number(limit) : 0,&lt;br /&gt;
            concurrency: 16,&lt;br /&gt;
            persistent: false&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationRuntime() {&lt;br /&gt;
        return window.Decorations || window.CLBI_DECORATIONS || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationRuntime() {&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var tries = 0;&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var runtime = getDecorationRuntime();&lt;br /&gt;
                if (runtime) return resolve(runtime);&lt;br /&gt;
                tries += 1;&lt;br /&gt;
                if (tries &amp;gt; 40) return resolve(null);&lt;br /&gt;
                window.setTimeout(tick, 25);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesDecorationEntry(entry, filter) {&lt;br /&gt;
        var era = String(filter &amp;amp;&amp;amp; filter.era || &#039;&#039;).trim();&lt;br /&gt;
        var page = String(filter &amp;amp;&amp;amp; filter.page || &#039;&#039;).trim();&lt;br /&gt;
        var entryPage = String(entry &amp;amp;&amp;amp; entry.page || &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039;) return false;&lt;br /&gt;
        if (page &amp;amp;&amp;amp; entryPage &amp;amp;&amp;amp; entryPage !== page) return false;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() !== era) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDecorationSet(task, level) {&lt;br /&gt;
        var ref = task.ref || &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
        return window.EntryStore.fetchJsonRef(ref, { noStore: !!task.noStore }).then(function (registry) {&lt;br /&gt;
            var list = registry &amp;amp;&amp;amp; Array.isArray(registry.decorations) ? registry.decorations : [];&lt;br /&gt;
            var pixelRefs = [];&lt;br /&gt;
            list.forEach(function (entry) {&lt;br /&gt;
                var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).toLowerCase();&lt;br /&gt;
                var asset = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
                if (!asset) return;&lt;br /&gt;
                if (type !== &#039;pixel-json&#039; &amp;amp;&amp;amp; !/\.json(?:[?#].*)?$/i.test(asset)) return;&lt;br /&gt;
                if (!matchesDecorationEntry(entry, task)) return;&lt;br /&gt;
                pixelRefs.push(asset);&lt;br /&gt;
            });&lt;br /&gt;
            if (level !== &#039;full&#039; || task.preparePixels === false || !pixelRefs.length) return registry;&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (!runtime || typeof runtime.preparePixelCanvas !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                        return window.EntryStore.fetchJsonRef(pixelRef).catch(function () { return null; });&lt;br /&gt;
                    })).then(function () { return registry; });&lt;br /&gt;
                }&lt;br /&gt;
                return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(pixelRef).catch(function () { return null; });&lt;br /&gt;
                })).then(function () { return registry; });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareNationsEra(task, level) {&lt;br /&gt;
        var era = String(task.era || &#039;1950&#039;);&lt;br /&gt;
        var listRef = task.listRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_List.json&#039;);&lt;br /&gt;
        var linkRef = task.linkMapRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_Link_Map.json&#039;);&lt;br /&gt;
        var jsonPhaseId = BootPerf.start(&#039;nations-era json fetch&#039;, { era: era, level: level, listRef: listRef, linkRef: linkRef });&lt;br /&gt;
        var listPromise = window.EntryStore.fetchJsonRef(listRef).catch(function () { return null; });&lt;br /&gt;
        var linkPromise = window.EntryStore.fetchJsonRef(linkRef).catch(function () { return null; });&lt;br /&gt;
&lt;br /&gt;
        return Promise.all([listPromise, linkPromise]).then(function (results) {&lt;br /&gt;
            BootPerf.end(jsonPhaseId, { ok: true });&lt;br /&gt;
            var files;&lt;br /&gt;
            var flagUrls;&lt;br /&gt;
            if (level !== &#039;full&#039; &amp;amp;&amp;amp; level !== &#039;warm&#039;) return results;&lt;br /&gt;
            files = unique(collectFlagsFromNationPayload(results[0]).concat(collectFlagsFromLinkMap(results[1])));&lt;br /&gt;
            return BootPerf.measure(&#039;nations-era flag url resolve&#039;, { era: era, count: files.length }, function () {&lt;br /&gt;
                return window.EntryStore.resolveFlagUrls(files);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                if (level === &#039;warm&#039;) {&lt;br /&gt;
                    /*&lt;br /&gt;
                     * Warm tab contract — 20260708.&lt;br /&gt;
                     *&lt;br /&gt;
                     * A previous tab already stored the same-revision flag URL and image&lt;br /&gt;
                     * blobs.  Re-decoding every flag here makes the second tab feel as slow&lt;br /&gt;
                     * as the first one.  In warm mode we only hydrate the flag URL table from&lt;br /&gt;
                     * EntryCache so NationsPanel can render stable URLs immediately; image&lt;br /&gt;
                     * decode remains demand-driven by the live panel instead of blocking the&lt;br /&gt;
                     * boot screen.&lt;br /&gt;
                     */&lt;br /&gt;
                    return results;&lt;br /&gt;
                }&lt;br /&gt;
                flagUrls = files.map(function (file) { return window.EntryStore.getFlagUrl(file); }).filter(Boolean);&lt;br /&gt;
                /*&lt;br /&gt;
                 * 20260710: Flag URLs are part of the entry contract, but decoding every&lt;br /&gt;
                 * flag image is not.  The 1950 list has more than 150 flags, and waiting&lt;br /&gt;
                 * for all image decodes kept the public boot gate open for several seconds.&lt;br /&gt;
                 * Keep revision-aware URLs ready for the panel, then let the browser load&lt;br /&gt;
                 * the actual images from the live DOM.  A low-priority background warm-up&lt;br /&gt;
                 * may run after the boot gate has opened, so it never competes with the&lt;br /&gt;
                 * current entry surface or the globe ready contract.&lt;br /&gt;
                 */&lt;br /&gt;
                BootPerf.mark(&#039;nations-era flag image prewarm skipped&#039;, {&lt;br /&gt;
                    era: era,&lt;br /&gt;
                    count: flagUrls.length,&lt;br /&gt;
                    limit: task.flagLimit == null ? 0 : Number(task.flagLimit),&lt;br /&gt;
                    reason: &#039;deferred-after-boot-gate&#039;&lt;br /&gt;
                });&lt;br /&gt;
                if (flagUrls.length &amp;amp;&amp;amp; task.deferFlagImages !== false) {&lt;br /&gt;
                    deferredEntryWarmups.push(function () {&lt;br /&gt;
                        return BootPerf.measure(&#039;deferred nations-era flag image prewarm&#039;, {&lt;br /&gt;
                            era: era,&lt;br /&gt;
                            count: flagUrls.length,&lt;br /&gt;
                            limit: task.flagLimit == null ? 0 : Number(task.flagLimit)&lt;br /&gt;
                        }, function () {&lt;br /&gt;
                            return prewarmImages(flagUrls, task.flagLimit == null ? 0 : Number(task.flagLimit));&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                return results;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareGlobeSharedAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;globe-shared url resolve&#039;, { count: refs.length, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            /*&lt;br /&gt;
             * Globe shared assets are heavy 6K texture sources.  The live&lt;br /&gt;
             * NationsGlobe ready contract already waits for the actual Three.js&lt;br /&gt;
             * texture path, so decoding the same images here only duplicates work&lt;br /&gt;
             * and holds the boot screen.  Keep revision-aware URL resolution in the&lt;br /&gt;
             * entry ledger, but make the image warm-up non-blocking.&lt;br /&gt;
             */&lt;br /&gt;
            BootPerf.mark(&#039;globe-shared image prewarm skipped&#039;, {&lt;br /&gt;
                count: urls.length,&lt;br /&gt;
                level: level,&lt;br /&gt;
                reason: &#039;deferred-to-nations-globe-ready-contract&#039;&lt;br /&gt;
            });&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (!window.EntryStore || typeof window.EntryStore.preloadImages !== &#039;function&#039;) return;&lt;br /&gt;
                window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 4)),&lt;br /&gt;
                    persistent: false&lt;br /&gt;
                }).catch(function () {});&lt;br /&gt;
            }, 0);&lt;br /&gt;
            return urls;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareImageAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        var full = level === &#039;full&#039;;&lt;br /&gt;
        var exposeAs = String(task &amp;amp;&amp;amp; task.exposeAs || &#039;&#039;).trim();&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;image-assets url resolve&#039;, { count: refs.length, exposeAs: exposeAs, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            if (exposeAs === &#039;nationsGlobeLoadingGif&#039;) {&lt;br /&gt;
                window.NationsGlobeLoadingGifRef = refs[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifUrl = urls[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifFile = &#039;Gfx-vhs-glitch-001.gif&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (!full) return urls;&lt;br /&gt;
            return BootPerf.measure(&#039;image-assets image prewarm&#039;, { count: urls.length, exposeAs: exposeAs, limit: task.imageLimit == null ? 0 : Number(task.imageLimit), concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2)) }, function () {&lt;br /&gt;
                return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2))&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () { return urls; });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareHtmlEntry(task) {&lt;br /&gt;
        var url = String(task &amp;amp;&amp;amp; (task.url || task.ref) || &#039;&#039;).trim();&lt;br /&gt;
        if (!url) return Promise.resolve(null);&lt;br /&gt;
        return window.EntryStore.fetchTextUrl(url, { noStore: !!task.noStore, resourceRef: task.resourceRef || task.ref || task.title || &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareTask(task, defaultLevel) {&lt;br /&gt;
        var level = String(task &amp;amp;&amp;amp; (task.level || defaultLevel) || &#039;half&#039;).toLowerCase();&lt;br /&gt;
        var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
        if (!task || typeof task !== &#039;object&#039;) return Promise.resolve(null);&lt;br /&gt;
        if (type === &#039;html&#039;) return prepareHtmlEntry(task);&lt;br /&gt;
        if (type === &#039;json&#039;) return window.EntryStore.fetchJsonRef(task.ref, { noStore: !!task.noStore });&lt;br /&gt;
        if (type === &#039;pixel-json&#039;) {&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (level === &#039;full&#039; &amp;amp;&amp;amp; runtime &amp;amp;&amp;amp; typeof runtime.preparePixelCanvas === &#039;function&#039;) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(task.ref || task.asset);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(task.ref || task.asset);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        if (type === &#039;decorations&#039;) return prepareDecorationSet(task, level);&lt;br /&gt;
        if (type === &#039;nations-era&#039;) return prepareNationsEra(task, level);&lt;br /&gt;
        if (type === &#039;globe-shared-assets&#039;) return prepareGlobeSharedAssets(task, level);&lt;br /&gt;
        if (type === &#039;image-assets&#039;) return prepareImageAssets(task, level);&lt;br /&gt;
        return Promise.resolve(null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifest() {&lt;br /&gt;
        return BootPerf.measure(&#039;revision manifest load&#039;, {}, function () {&lt;br /&gt;
            return (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.load === &#039;function&#039; ? window.RevisionManifest.load() : Promise.resolve(null));&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;entry manifest json fetch&#039;, { ref: MANIFEST_TITLE }, function () {&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(MANIFEST_TITLE, { noStore: false });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (manifest) {&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039; || !manifest.version) {&lt;br /&gt;
                BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: &#039;invalid manifest&#039; });&lt;br /&gt;
                return defaultManifest;&lt;br /&gt;
            }&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest ready&#039;, { version: manifest.version });&lt;br /&gt;
            return manifest;&lt;br /&gt;
        }).catch(function (err) {&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: err &amp;amp;&amp;amp; (err.message || String(err)) || &#039;load failed&#039; });&lt;br /&gt;
            return defaultManifest;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function flattenInitialTasks(manifest) {&lt;br /&gt;
        var initial = manifest &amp;amp;&amp;amp; manifest.initial ? manifest.initial : {};&lt;br /&gt;
        var full = Array.isArray(initial.full) ? initial.full : [];&lt;br /&gt;
        var half = Array.isArray(initial.half) ? initial.half : [];&lt;br /&gt;
        var tasks = [];&lt;br /&gt;
        full.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;full&#039;;&lt;br /&gt;
            task.blocking = true;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        half.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;half&#039;;&lt;br /&gt;
            task.blocking = false;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        return tasks;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function flattenWarmInitialTasks(tasks) {&lt;br /&gt;
        var warmed = [];&lt;br /&gt;
        (tasks || []).forEach(function (task) {&lt;br /&gt;
            var copy;&lt;br /&gt;
            var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
            if (!task || task.blocking === false) return;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Warm boot fast path — 20260708.&lt;br /&gt;
             *&lt;br /&gt;
             * Cold boot intentionally performs the expensive work: resolving every flag,&lt;br /&gt;
             * downloading/decoding images, preparing decoration canvases, and warming shared&lt;br /&gt;
             * globe textures.  Once a tab has certified the pack against the current revision&lt;br /&gt;
             * manifest, later tabs must not replay that full workload.  They only hydrate the&lt;br /&gt;
             * small tables needed by the live page and let already-cached image/blob data be&lt;br /&gt;
             * consumed on demand.  This is the missing layer that made second tabs feel almost&lt;br /&gt;
             * as slow as first tabs even though the string checks were true.&lt;br /&gt;
             */&lt;br /&gt;
            if (type === &#039;globe-shared-assets&#039;) return;&lt;br /&gt;
            if (type === &#039;image-assets&#039;) return;&lt;br /&gt;
            copy = Object.assign({}, task);&lt;br /&gt;
            if (type === &#039;nations-era&#039;) copy.level = &#039;warm&#039;;&lt;br /&gt;
            else if (type === &#039;decorations&#039;) {&lt;br /&gt;
                copy.level = &#039;half&#039;;&lt;br /&gt;
                copy.preparePixels = false;&lt;br /&gt;
            } else if (type === &#039;pixel-json&#039;) copy.level = &#039;half&#039;;&lt;br /&gt;
            warmed.push(copy);&lt;br /&gt;
        });&lt;br /&gt;
        return warmed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackKey(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        return &#039;initial-entry-full:&#039; + version;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackToken(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        var revToken = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(version) : version;&lt;br /&gt;
        return revToken;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isInitialPackWarm(manifest) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        return !!(window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.packReady === &#039;function&#039; &amp;amp;&amp;amp; window.EntryCache.packReady(key, token));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function markInitialPackWarm(manifest, meta) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        if (window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.setPackReady === &#039;function&#039;) {&lt;br /&gt;
            window.EntryCache.setPackReady(key, token, Object.assign({ manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || BUILD_ID }, meta || {}));&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function waitMs(ms) {&lt;br /&gt;
        return new Promise(function (resolve) { window.setTimeout(resolve, Math.max(0, ms || 0)); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForBody() {&lt;br /&gt;
        if (document.body) return Promise.resolve(document.body);&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                if (document.body) return resolve(document.body);&lt;br /&gt;
                window.setTimeout(tick, 10);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDomReady() {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) return Promise.resolve();&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, resolve, { once: true });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForAnimationFrames(count) {&lt;br /&gt;
        count = Math.max(1, Math.round(count || 1));&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function next(left) {&lt;br /&gt;
                if (left &amp;lt;= 0) return resolve();&lt;br /&gt;
                window.requestAnimationFrame(function () { next(left - 1); });&lt;br /&gt;
            }&lt;br /&gt;
            next(count);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitUntil(predicate, options) {&lt;br /&gt;
        var started = now();&lt;br /&gt;
        var timeout = options &amp;amp;&amp;amp; options.timeoutMs ? options.timeoutMs : 8000;&lt;br /&gt;
        var interval = options &amp;amp;&amp;amp; options.intervalMs ? options.intervalMs : 60;&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var result = false;&lt;br /&gt;
                try { result = predicate(); } catch (err) { result = false; }&lt;br /&gt;
                if (result) return resolve({ ok: true, value: result });&lt;br /&gt;
                if (now() - started &amp;gt;= timeout) return resolve({ ok: false, timeout: true });&lt;br /&gt;
                window.setTimeout(tick, interval);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForTrackedScripts() {&lt;br /&gt;
        var list = (window.EntryScriptLoads || []).slice();&lt;br /&gt;
        if (!list.length) return Promise.resolve([]);&lt;br /&gt;
        return Promise.all(list.map(function (promise) {&lt;br /&gt;
            return Promise.resolve(promise).catch(function (err) { return { ok: false, error: err }; });&lt;br /&gt;
        }));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDocumentSurface() {&lt;br /&gt;
        return waitForDomReady().then(function () {&lt;br /&gt;
            return waitForBody();&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                return document.querySelector(&#039;.content-wrapper&#039;) &amp;amp;&amp;amp; document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
            }, { timeoutMs: 8000, intervalMs: 50 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isNationsPageSurface() {&lt;br /&gt;
        var page = String(mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
        return !!document.querySelector(&#039;.clbi-nations-panel-stack&#039;) || /(?:^|[_ ])시대(?:$|[_ ])/.test(page) || /(?:^|[_ ])Era(?:$|[_ ])/i.test(page);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getInitialNationsEra(manifest) {&lt;br /&gt;
        var tasks = flattenInitialTasks(manifest);&lt;br /&gt;
        var i;&lt;br /&gt;
        for (i = 0; i &amp;lt; tasks.length; i += 1) {&lt;br /&gt;
            if (tasks[i] &amp;amp;&amp;amp; tasks[i].type === &#039;nations-era&#039; &amp;amp;&amp;amp; String(tasks[i].level || &#039;&#039;).toLowerCase() === &#039;full&#039;) {&lt;br /&gt;
                return String(tasks[i].era || &#039;1950&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;1950&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsPanelReady(era) {&lt;br /&gt;
        return BootPerf.measure(&#039;wait nations panel api&#039;, { era: era }, function () {&lt;br /&gt;
            return waitUntil(function () { return window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.whenEraReady === &#039;function&#039;; }, {&lt;br /&gt;
                timeoutMs: 8000,&lt;br /&gt;
                intervalMs: 50&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (result) {&lt;br /&gt;
            if (!result.ok || !window.NationsPanel || typeof window.NationsPanel.whenEraReady !== &#039;function&#039;) return null;&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel era ready&#039;, { era: era }, function () {&lt;br /&gt;
                return window.NationsPanel.whenEraReady(era, { timeoutMs: 15000 }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel dom ready&#039;, { era: era }, function () {&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var panel = document.querySelector(&#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + era + &#039;&amp;quot;] .clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;) ||&lt;br /&gt;
                        document.querySelector(&#039;.clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
                    return panel &amp;amp;&amp;amp; panel.classList.contains(&#039;clbi-nations-list-json-ready&#039;) &amp;amp;&amp;amp; panel.getAttribute(&#039;data-nation-list-year-loaded&#039;) === String(era);&lt;br /&gt;
                }, { timeoutMs: 15000, intervalMs: 80 });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsGlobeReady(options) {&lt;br /&gt;
        var globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
        var soft;&lt;br /&gt;
        var timeout;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        soft = !!options.soft;&lt;br /&gt;
        timeout = Number(options.timeoutMs || (soft ? 450 : 30000));&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            BootPerf.mark(&#039;wait nations globe skipped&#039;, { reason: &#039;no globe node&#039;, soft: soft });&lt;br /&gt;
            return Promise.resolve(null);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return BootPerf.measure(soft ? &#039;wait nations globe warm attach&#039; : &#039;wait nations globe ready contract&#039;, { soft: soft, timeoutMs: timeout }, function () {&lt;br /&gt;
            if (soft) {&lt;br /&gt;
                /*&lt;br /&gt;
                Warm-tab policy:&lt;br /&gt;
                Raw globe images are already revision-checked and blob-cached by EntryCache,&lt;br /&gt;
                but WebGL scene creation, GPU upload, topojson parsing, and per-tab Three.js&lt;br /&gt;
                objects cannot be shared across browser tabs.  Waiting for the full is-ready&lt;br /&gt;
                contract here makes a warm tab slower than the first tab.  In warm mode the&lt;br /&gt;
                initial gate only waits until the globe consumer has been attached, then the&lt;br /&gt;
                globe finishes its own per-tab hydrate without holding the whole page hostage.&lt;br /&gt;
                */&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                    if (!node) return false;&lt;br /&gt;
                    return node.getAttribute(&#039;data-nations-globe-ready&#039;) === &#039;1&#039; ||&lt;br /&gt;
                        !!node.CLBI_NationsGlobeInstance ||&lt;br /&gt;
                        !!node.querySelector(&#039;.clbi-nations-globe-stage&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;is-ready&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;has-error&#039;);&lt;br /&gt;
                }, { timeoutMs: timeout, intervalMs: 40 });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                return node &amp;amp;&amp;amp; (node.classList.contains(&#039;is-ready&#039;) || node.classList.contains(&#039;has-error&#039;));&lt;br /&gt;
            }, { timeoutMs: timeout, intervalMs: 100 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationsReady(options) {&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
            if (!runtime) return null;&lt;br /&gt;
            if (options.warm) {&lt;br /&gt;
                /*&lt;br /&gt;
                 * Warm tab contract — 20260708.&lt;br /&gt;
                 *&lt;br /&gt;
                 * The decoration registry and pixel canvases were prepared by the cold tab.&lt;br /&gt;
                 * Calling reload() here re-reads and re-syncs the decoration layer during the&lt;br /&gt;
                 * boot gate, which is visible as a pointless delay.  A warm tab only needs the&lt;br /&gt;
                 * current visibility pass so boot can release quickly and editing/preview tools&lt;br /&gt;
                 * remain responsive.&lt;br /&gt;
                 */&lt;br /&gt;
                try {&lt;br /&gt;
                    if (typeof runtime.updateVisibility === &#039;function&#039;) runtime.updateVisibility(document);&lt;br /&gt;
                    else if (typeof runtime.sync === &#039;function&#039;) runtime.sync();&lt;br /&gt;
                } catch (err) {}&lt;br /&gt;
                return waitForAnimationFrames(1);&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof runtime.reload !== &#039;function&#039;) return null;&lt;br /&gt;
            return runtime.reload().catch(function () { return null; }).then(function () {&lt;br /&gt;
                return waitForAnimationFrames(2);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForCurrentEntrySurface(manifest, options) {&lt;br /&gt;
        var era = getInitialNationsEra(manifest);&lt;br /&gt;
        var warmPack;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        warmPack = !!options.warmPack;&lt;br /&gt;
&lt;br /&gt;
        if (!isNationsPageSurface()) {&lt;br /&gt;
            return waitForDecorationsReady({ warm: !!warmPack }).then(function () { return waitForAnimationFrames(1); });&lt;br /&gt;
        }&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = warmPack ? &#039;Hydrating current information surface from warm cache&#039; : &#039;Preparing current information surface&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for era &#039; + era + &#039; ready contract&#039;;&lt;br /&gt;
        return waitForNationsPanelReady(era)&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (warmPack) {&lt;br /&gt;
                    if (bootDetailNode) bootDetailNode.textContent = &#039;attaching globe warm consumer&#039;;&lt;br /&gt;
                    return waitForNationsGlobeReady({ soft: true, timeoutMs: 450 });&lt;br /&gt;
                }&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for globe ready contract&#039;;&lt;br /&gt;
                return waitForNationsGlobeReady();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for decoration surface&#039;;&lt;br /&gt;
                return waitForDecorationsReady({ warm: warmPack });&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () { return waitForAnimationFrames(warmPack ? 1 : 2); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runDeferredEntryWarmups() {&lt;br /&gt;
        var queue = deferredEntryWarmups.splice(0);&lt;br /&gt;
        if (!queue.length) return;&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            var chain = Promise.resolve();&lt;br /&gt;
            queue.forEach(function (job) {&lt;br /&gt;
                chain = chain.then(function () {&lt;br /&gt;
                    try { return job(); }&lt;br /&gt;
                    catch (err) { return null; }&lt;br /&gt;
                }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }, 1200);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runInitialLoad(options) {&lt;br /&gt;
        var done = 0;&lt;br /&gt;
        var manifestRef;&lt;br /&gt;
        var tasks;&lt;br /&gt;
        var bootOptions;&lt;br /&gt;
        var timedOut = false;&lt;br /&gt;
        var warmPack = false;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        bootStartTime = window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.startTime ? window.__BootGatePrelude.startTime : now();&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        ensureBootScreen();&lt;br /&gt;
        updateBootProgress(0, 1, &#039;loading entry manifest&#039;);&lt;br /&gt;
&lt;br /&gt;
        manifestRef = loadManifest().then(function (manifest) {&lt;br /&gt;
            var minDisplay;&lt;br /&gt;
            var totalSteps;&lt;br /&gt;
            var maxBlockingMs;&lt;br /&gt;
            var timeoutHandle;&lt;br /&gt;
            bootOptions = manifest.boot || {};&lt;br /&gt;
            warmPack = isInitialPackWarm(manifest);&lt;br /&gt;
            minDisplay = options.minDisplayMs || (warmPack ? (bootOptions.cachedMinDisplayMs || 220) : (bootOptions.minDisplayMs || 950));&lt;br /&gt;
            tasks = flattenInitialTasks(manifest);&lt;br /&gt;
            if (!tasks.length) tasks = flattenInitialTasks(defaultManifest);&lt;br /&gt;
            if (warmPack) tasks = flattenWarmInitialTasks(tasks);&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            Full/half contract:&lt;br /&gt;
            These manifest tasks only prepare data assets.  The gate is not allowed to open&lt;br /&gt;
            until the current page surface has consumed that data and reported a real ready&lt;br /&gt;
            state below.  Do not reintroduce localStorage-only skip logic here; a cached&lt;br /&gt;
            version number is not the same as current-tab readiness.&lt;br /&gt;
            */&lt;br /&gt;
            totalSteps = tasks.length + 4;&lt;br /&gt;
            updateBootProgress(0, totalSteps, warmPack ? &#039;hydrating warm entry cache&#039; : &#039;starting entry packs&#039;);&lt;br /&gt;
&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                maxBlockingMs = Number(options.maxBlockingMs || bootOptions.maxBlockingMs || 30000);&lt;br /&gt;
                timeoutHandle = window.setTimeout(function () {&lt;br /&gt;
                    timedOut = true;&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }, maxBlockingMs);&lt;br /&gt;
&lt;br /&gt;
                Promise.all(tasks.map(function (task) {&lt;br /&gt;
                    var label = task.label || task.id || task.type || &#039;entry task&#039;;&lt;br /&gt;
                    return BootPerf.measure(&#039;boot task: &#039; + label, { id: task.id || &#039;&#039;, type: task.type || &#039;&#039;, level: task.level || task.level === 0 ? task.level : &#039;&#039; }, function () {&lt;br /&gt;
                        return prepareTask(task, task.level);&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }).then(function () {&lt;br /&gt;
                        done += 1;&lt;br /&gt;
                        updateBootProgress(done, totalSteps, label);&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;loading subsystem scripts&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait tracked subsystem scripts&#039;, { count: (window.EntryScriptLoads || []).length }, function () { return waitForTrackedScripts(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for document shell&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait document shell&#039;, {}, function () { return waitForDocumentSurface(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for current entry surface&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait current entry surface&#039;, { warmPack: warmPack }, function () { return waitForCurrentEntrySurface(manifest, { warmPack: warmPack }); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(totalSteps, totalSteps, warmPack ? &#039;warm entry surface complete&#039; : &#039;entry surface complete&#039;);&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                var elapsed = now() - bootStartTime;&lt;br /&gt;
                var wait = Math.max(0, minDisplay - elapsed);&lt;br /&gt;
                return waitMs(wait);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                localStorage.setItem(READY_KEY, String(manifest.version));&lt;br /&gt;
                if (!timedOut) markInitialPackWarm(manifest, { warmSource: warmPack ? &#039;cache-hit&#039; : &#039;cold-build&#039; });&lt;br /&gt;
                if (bootStatusNode) bootStatusNode.textContent = timedOut ? &#039;Entry surface ready with deferred items&#039; : (warmPack ? &#039;Entry surface ready from warm cache&#039; : &#039;Entry surface ready&#039;);&lt;br /&gt;
                updateBootProgress(tasks.length + 4, tasks.length + 4, timedOut ? &#039;timeout: continuing deferred loading&#039; : (warmPack ? &#039;warm cache complete&#039; : &#039;complete&#039;));&lt;br /&gt;
                BootPerf.mark(&#039;boot gate complete&#039;, { timedOut: timedOut, warmPack: warmPack, manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || &#039;&#039; });&lt;br /&gt;
                BootPerf.print();&lt;br /&gt;
                runDeferredEntryWarmups();&lt;br /&gt;
                window.setTimeout(function () {&lt;br /&gt;
                    if (requiresLoginGate()) showLoginGate();&lt;br /&gt;
                    else hideBootScreen();&lt;br /&gt;
                }, 120);&lt;br /&gt;
                return manifest;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return manifestRef;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startBoot(options) {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return Promise.resolve({ skipped: true, reason: &#039;developer-or-editing-page&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        if (hasBootParam(&#039;0&#039;) &amp;amp;&amp;amp; !requiresLoginGate() &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return Promise.resolve(null);&lt;br /&gt;
        if (bootStarted &amp;amp;&amp;amp; bootPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return bootPromise;&lt;br /&gt;
        bootStarted = true;&lt;br /&gt;
        bootPromise = runInitialLoad(options || {});&lt;br /&gt;
        return bootPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resetBoot() {&lt;br /&gt;
        localStorage.removeItem(READY_KEY);&lt;br /&gt;
        bootStarted = false;&lt;br /&gt;
        bootPromise = null;&lt;br /&gt;
        loginGateLocked = false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    BootGate intentionally has no SPA hold/release API.&lt;br /&gt;
    Initial boot is the only blocking phase; later SPA routes must consume prepared&lt;br /&gt;
    EntryStore artifacts without reopening the loading surface.&lt;br /&gt;
    */&lt;br /&gt;
&lt;br /&gt;
    function buildBootReport() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD_ID,&lt;br /&gt;
            state: {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                excludedPage: BOOT_EXCLUDED_PAGE,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;))&lt;br /&gt;
            },&lt;br /&gt;
            store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
            bootPerf: window.BootPerf &amp;amp;&amp;amp; typeof window.BootPerf.summary === &#039;function&#039; ? window.BootPerf.summary() : null,&lt;br /&gt;
            revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null,&lt;br /&gt;
            nations: window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.diagnostics === &#039;function&#039; ? window.NationsPanel.diagnostics() : null,&lt;br /&gt;
            decorations: window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.diagnostics === &#039;function&#039; ? window.Decorations.diagnostics() : null&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryLoader = window.EntryLoader || {&lt;br /&gt;
        loadManifest: loadManifest,&lt;br /&gt;
        prepareTask: prepareTask,&lt;br /&gt;
        prepareNationsEra: prepareNationsEra,&lt;br /&gt;
        prepareDecorationSet: prepareDecorationSet,&lt;br /&gt;
        prepareGlobeSharedAssets: prepareGlobeSharedAssets,&lt;br /&gt;
        prepareImageAssets: prepareImageAssets,&lt;br /&gt;
        prepareHtmlEntry: prepareHtmlEntry,&lt;br /&gt;
        runInitialLoad: runInitialLoad&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function showBootPreview(options) {&lt;br /&gt;
        var node;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        /*&lt;br /&gt;
         * Boot preview is a design/editing surface, not a real boot gate.&lt;br /&gt;
         * The earlier preview reused activateBootSurface(), which applied&lt;br /&gt;
         * html.boot-gate-active and hid the entire wiki shell, including&lt;br /&gt;
         * DevTools.  That made it impossible to edit loading-screen&lt;br /&gt;
         * decorations while previewing them.  Keep the real first-entry gate&lt;br /&gt;
         * full-screen, but make preview a small non-blocking surface below the&lt;br /&gt;
         * DevTools z-index so the owner can keep using the editor.&lt;br /&gt;
         */&lt;br /&gt;
        node = ensureBootScreen({ preview: true });&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        node.classList.add(&#039;is-preview&#039;);&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = options.status || &#039;Loading screen preview&#039;;&lt;br /&gt;
        updateBootProgress(Number(options.progress || 64), 100, options.detail || &#039;preview mode: no entry tasks are running&#039;);&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BootGate = window.BootGate || {&lt;br /&gt;
        version: BUILD_ID,&lt;br /&gt;
        start: startBoot,&lt;br /&gt;
        show: function (options) {&lt;br /&gt;
            options = options || {};&lt;br /&gt;
            options.force = true;&lt;br /&gt;
            resetBoot();&lt;br /&gt;
            return startBoot(options);&lt;br /&gt;
        },&lt;br /&gt;
        reset: resetBoot,&lt;br /&gt;
        hide: hideBootScreen,&lt;br /&gt;
        preview: showBootPreview,&lt;br /&gt;
        report: buildBootReport,&lt;br /&gt;
        state: function () {&lt;br /&gt;
            return {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                prelude: !!window.__BootGatePrelude,&lt;br /&gt;
                store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
                revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function prime() {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        waitForBody().then(function () {&lt;br /&gt;
            ensureBootScreen();&lt;br /&gt;
            return waitForAnimationFrames(1);&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            startBoot();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    waitForBody().then(function () {&lt;br /&gt;
        if (!BOOT_EXCLUDED_PAGE) ensureBootScreen();&lt;br /&gt;
        else hideBootScreen();&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(prime, 0);&lt;br /&gt;
})(window, document, window.mediaWiki || window.mw);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:DevTools.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsPanel.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsGlobe.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* CLBI safety guard: adaptive reset functions must exist before shell metric callbacks run. */&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.resetLeftRecentAdaptiveState = resetLeftRecentAdaptiveState;&lt;br /&gt;
window.resetLeftBillboardAdaptiveState = resetLeftBillboardAdaptiveState;&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:AnecdoteViewer.js&#039;);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SYSTEM_TITLE_NAMESPACES = {&lt;br /&gt;
        &#039;-1&#039;: true,&lt;br /&gt;
        &#039;4&#039;: true,&lt;br /&gt;
        &#039;5&#039;: true,&lt;br /&gt;
        &#039;6&#039;: true,&lt;br /&gt;
        &#039;7&#039;: true,&lt;br /&gt;
        &#039;8&#039;: true,&lt;br /&gt;
        &#039;9&#039;: true,&lt;br /&gt;
        &#039;10&#039;: true,&lt;br /&gt;
        &#039;11&#039;: true,&lt;br /&gt;
        &#039;12&#039;: true,&lt;br /&gt;
        &#039;13&#039;: true,&lt;br /&gt;
        &#039;14&#039;: true,&lt;br /&gt;
        &#039;15&#039;: true,&lt;br /&gt;
        &#039;828&#039;: true,&lt;br /&gt;
        &#039;829&#039;: true&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function normalizePageNameForShell(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readCurrentPageNameForShell() {&lt;br /&gt;
        var pageName = mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (pageName) {&lt;br /&gt;
            return normalizePageNameForShell(pageName);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return normalizePageNameForShell(window.location.pathname || &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnecdoteNamespaceForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var canonicalNamespace = String(mw.config.get(&#039;wgCanonicalNamespace&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 3000 ||&lt;br /&gt;
            canonicalNamespace === &#039;anecdote&#039; ||&lt;br /&gt;
            /^(anecdote|에넥도트):/i.test(pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBackendOrSystemPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var action = String(mw.config.get(&#039;wgAction&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var lowerPageName = pageName.toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === &#039;대문&#039;) {&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (SYSTEM_TITLE_NAMESPACES[String(namespaceNumber)]) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;json&#039; || contentModel === &#039;sanitized-css&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/\.(css|js|json)$/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/^(mediawiki|미디어위키|special|특수):/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMediaWikiSystemAssetPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 8 &amp;amp;&amp;amp;&lt;br /&gt;
            (/\.(css|js)$/i.test(pageName) || contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;sanitized-css&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var systemDocRawFetchToken = 0;&lt;br /&gt;
&lt;br /&gt;
    function cleanupLegacySystemDocCodeMutationsForShell() {&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codepane&#039;).forEach(function (pane) {&lt;br /&gt;
            var parent;&lt;br /&gt;
&lt;br /&gt;
            if (!pane || !pane.parentNode) return;&lt;br /&gt;
&lt;br /&gt;
            parent = pane.parentNode;&lt;br /&gt;
            while (pane.firstChild) {&lt;br /&gt;
                parent.insertBefore(pane.firstChild, pane);&lt;br /&gt;
            }&lt;br /&gt;
            parent.removeChild(pane);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codebox&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;style&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocOutputForShell() {&lt;br /&gt;
        return document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findSystemDocSourceNodeForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var children;&lt;br /&gt;
        var preferred;&lt;br /&gt;
&lt;br /&gt;
        if (!output) return null;&lt;br /&gt;
&lt;br /&gt;
        children = Array.prototype.slice.call(output.children || [])&lt;br /&gt;
            .filter(function (el) {&lt;br /&gt;
                return el &amp;amp;&amp;amp; el.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-doc-indicator-row&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-source-viewer&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    !el.classList.contains(&#039;catlinks&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    (el.textContent || &#039;&#039;).trim().length &amp;gt; 200;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
        preferred = children.filter(function (el) {&lt;br /&gt;
            return el.matches &amp;amp;&amp;amp; el.matches(&#039;.mw-highlight, .mw-code, pre&#039;);&lt;br /&gt;
        })[0];&lt;br /&gt;
&lt;br /&gt;
        return preferred || children.sort(function (a, b) {&lt;br /&gt;
            return (b.textContent || &#039;&#039;).trim().length - (a.textContent || &#039;&#039;).trim().length;&lt;br /&gt;
        })[0] || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocRawUrlForShell() {&lt;br /&gt;
        var title = mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell();&lt;br /&gt;
        var url;&lt;br /&gt;
&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            url = mw.util.getUrl(title, {&lt;br /&gt;
                action: &#039;raw&#039;,&lt;br /&gt;
                ctype: &#039;text/plain&#039;&lt;br /&gt;
            });&lt;br /&gt;
        } else {&lt;br /&gt;
            url = &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/plain&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, title);&lt;br /&gt;
        }&lt;br /&gt;
        return url;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (viewer &amp;amp;&amp;amp; viewer.parentNode) {&lt;br /&gt;
            viewer.parentNode.removeChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-original-source-hidden&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-source-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureSystemDocSourceViewerForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var source;&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var fallbackText;&lt;br /&gt;
&lt;br /&gt;
        if (!output || !isMediaWikiSystemAssetPageForShell()) return null;&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
&lt;br /&gt;
        source = findSystemDocSourceNodeForShell();&lt;br /&gt;
        if (!source) return null;&lt;br /&gt;
&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!viewer) {&lt;br /&gt;
            viewer = document.createElement(&#039;pre&#039;);&lt;br /&gt;
            viewer.id = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            viewer.className = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            output.appendChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fallbackText = source.textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!viewer.textContent &amp;amp;&amp;amp; fallbackText) {&lt;br /&gt;
            viewer.textContent = fallbackText;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        source.classList.add(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
        source.setAttribute(&#039;data-clbi-system-source-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        source.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
        return viewer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var token;&lt;br /&gt;
        var currentScrollTop;&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = String(mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell());&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        시스템 문서 뷰어가 이미 만들어져 있고 raw 원문도 로드된 상태라면&lt;br /&gt;
        다시 source 탐색/숨김/스타일 재적용을 하지 않는다.&lt;br /&gt;
        DevTools Elements 패널에서 body가 계속 파랗게 깜빡이던 원인은&lt;br /&gt;
        MutationObserver가 이 재적용을 반복해서 DOM attribute mutation을 만들었기 때문이다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            viewer &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-title&#039;) === pageName &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-loaded&#039;) === &#039;1&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        viewer = ensureSystemDocSourceViewerForShell();&lt;br /&gt;
        if (!viewer) return;&lt;br /&gt;
&lt;br /&gt;
        currentScrollTop = viewer.scrollTop || 0;&lt;br /&gt;
        viewer.setAttribute(&#039;data-clbi-raw-title&#039;, pageName);&lt;br /&gt;
        token = ++systemDocRawFetchToken;&lt;br /&gt;
&lt;br /&gt;
        fetch(getSystemDocRawUrlForShell(), { credentials: &#039;same-origin&#039; })&lt;br /&gt;
            .then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;raw fetch failed &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (token !== systemDocRawFetchToken) return;&lt;br /&gt;
&lt;br /&gt;
                currentScrollTop = viewer.scrollTop || currentScrollTop || 0;&lt;br /&gt;
&lt;br /&gt;
                if (text &amp;amp;&amp;amp; viewer.textContent !== text) {&lt;br /&gt;
                    viewer.textContent = text;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;1&#039;);&lt;br /&gt;
                viewer.scrollTop = currentScrollTop;&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;0&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocIndicatorForShell() {&lt;br /&gt;
        var existing = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            document.body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (existing &amp;amp;&amp;amp; existing.parentNode) {&lt;br /&gt;
            existing.parentNode.removeChild(existing);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        removeSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocIndicatorForShell() {&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var extMatch;&lt;br /&gt;
        var ext;&lt;br /&gt;
        var row;&lt;br /&gt;
        var box;&lt;br /&gt;
        var meta;&lt;br /&gt;
        var label;&lt;br /&gt;
        var type;&lt;br /&gt;
        var title;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var main;&lt;br /&gt;
&lt;br /&gt;
        if (!document.body || !isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = readCurrentPageNameForShell();&lt;br /&gt;
        extMatch = pageName.match(/\.(css|js)$/i);&lt;br /&gt;
        ext = extMatch ? extMatch[1].toUpperCase() : &#039;DOC&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.classList.add(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        row = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!row) {&lt;br /&gt;
            row = document.createElement(&#039;div&#039;);&lt;br /&gt;
            row.id = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
            row.className = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
&lt;br /&gt;
            box = document.createElement(&#039;div&#039;);&lt;br /&gt;
            box.className = &#039;clbi-system-doc-indicator&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta = document.createElement(&#039;div&#039;);&lt;br /&gt;
            meta.className = &#039;clbi-system-doc-meta&#039;;&lt;br /&gt;
&lt;br /&gt;
            label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            label.className = &#039;clbi-system-doc-label&#039;;&lt;br /&gt;
            label.textContent = &#039;SYSTEM DOCUMENT&#039;;&lt;br /&gt;
&lt;br /&gt;
            type = document.createElement(&#039;span&#039;);&lt;br /&gt;
            type.className = &#039;clbi-system-doc-type&#039;;&lt;br /&gt;
&lt;br /&gt;
            title = document.createElement(&#039;div&#039;);&lt;br /&gt;
            title.className = &#039;clbi-system-doc-title&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta.appendChild(label);&lt;br /&gt;
            meta.appendChild(type);&lt;br /&gt;
            box.appendChild(meta);&lt;br /&gt;
            box.appendChild(title);&lt;br /&gt;
            row.appendChild(box);&lt;br /&gt;
&lt;br /&gt;
            anchor = getSystemDocOutputForShell();&lt;br /&gt;
            main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (anchor &amp;amp;&amp;amp; anchor.parentNode) {&lt;br /&gt;
                anchor.parentNode.insertBefore(row, anchor);&lt;br /&gt;
            } else if (main) {&lt;br /&gt;
                main.insertBefore(row, main.firstChild);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        type = row.querySelector(&#039;.clbi-system-doc-type&#039;);&lt;br /&gt;
        title = row.querySelector(&#039;.clbi-system-doc-title&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (type) type.textContent = ext;&lt;br /&gt;
        if (title) title.textContent = pageName;&lt;br /&gt;
&lt;br /&gt;
        renderSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var PAGE_TITLE_TARGET_SELECTORS = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var pageShellObserverStarted = false;&lt;br /&gt;
    var pageShellObserverTimer = null;&lt;br /&gt;
&lt;br /&gt;
    function setPageTitleDomHidden(hidden) {&lt;br /&gt;
        var nodes = document.querySelectorAll(PAGE_TITLE_TARGET_SELECTORS.join(&#039;,&#039;));&lt;br /&gt;
&lt;br /&gt;
        nodes.forEach(function (node) {&lt;br /&gt;
            if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
            if (hidden) {&lt;br /&gt;
                node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
                node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
                node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClasses() {&lt;br /&gt;
        var body = document.body;&lt;br /&gt;
        var isSystemPage;&lt;br /&gt;
&lt;br /&gt;
        if (!body) return;&lt;br /&gt;
&lt;br /&gt;
        isSystemPage = isBackendOrSystemPageForShell();&lt;br /&gt;
&lt;br /&gt;
        body.classList.remove(&#039;page-title-hidden&#039;, &#039;page-title-visible&#039;, &#039;backend-system-page&#039;, &#039;anecdote-namespace-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            removeSystemDocIndicatorForShell();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isAnecdoteNamespaceForShell()) {&lt;br /&gt;
            body.classList.add(&#039;anecdote-namespace-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;, &#039;backend-system-page&#039;, &#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
            renderSystemDocIndicatorForShell();&lt;br /&gt;
        } else if (isSystemPage) {&lt;br /&gt;
            body.classList.add(&#039;page-title-visible&#039;, &#039;backend-system-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(false);&lt;br /&gt;
        } else {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClassesDeferred() {&lt;br /&gt;
        applyPageShellClasses();&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 0);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 80);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startPageShellObserver() {&lt;br /&gt;
        var observer;&lt;br /&gt;
&lt;br /&gt;
        if (pageShellObserverStarted || !window.MutationObserver || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
        pageShellObserverStarted = true;&lt;br /&gt;
        observer = new MutationObserver(function (mutations) {&lt;br /&gt;
            var i;&lt;br /&gt;
            var target;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            시스템 CSS/JS 문서는 applyPageShellClasses()가 초기에 한 번&lt;br /&gt;
            인디케이터와 source viewer를 만든 뒤에는 MutationObserver가 다시&lt;br /&gt;
            같은 렌더링을 반복할 필요가 없다. 이 반복이 DevTools에서 body/요소가&lt;br /&gt;
            계속 플래시되는 직접 원인이다.&lt;br /&gt;
            SPA 전환 뒤의 처리는 loadPage()와 wikipage.content hook에서 따로 호출된다.&lt;br /&gt;
            */&lt;br /&gt;
            if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
                for (i = 0; i &amp;lt; mutations.length; i += 1) {&lt;br /&gt;
                    target = mutations[i] &amp;amp;&amp;amp; mutations[i].target;&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        target &amp;amp;&amp;amp;&lt;br /&gt;
                        target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                        (&lt;br /&gt;
                            target.id === &#039;clbi-system-source-viewer&#039; ||&lt;br /&gt;
                            target.id === &#039;clbi-system-doc-indicator-row&#039; ||&lt;br /&gt;
                            (target.closest &amp;amp;&amp;amp; target.closest(&#039;#clbi-system-source-viewer, #clbi-system-doc-indicator-row&#039;))&lt;br /&gt;
                        )&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-doc-indicator-row&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-source-viewer&#039;)&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (pageShellObserverTimer) return;&lt;br /&gt;
&lt;br /&gt;
            pageShellObserverTimer = window.setTimeout(function () {&lt;br /&gt;
                pageShellObserverTimer = null;&lt;br /&gt;
                applyPageShellClasses();&lt;br /&gt;
            }, 50);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        observer.observe(document.body, {&lt;br /&gt;
            childList: true,&lt;br /&gt;
            subtree: true&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {&lt;br /&gt;
            applyPageShellClassesDeferred();&lt;br /&gt;
            startPageShellObserver();&lt;br /&gt;
        });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyPageShellClassesDeferred();&lt;br /&gt;
        startPageShellObserver();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(applyPageShellClassesDeferred);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_PAGE_SHELL = {&lt;br /&gt;
        refresh: applyPageShellClasses,&lt;br /&gt;
        isBackendOrSystemPage: isBackendOrSystemPageForShell,&lt;br /&gt;
        isSystemAssetPage: isMediaWikiSystemAssetPageForShell,&lt;br /&gt;
        renderSystemDocIndicator: renderSystemDocIndicatorForShell,&lt;br /&gt;
        removeSystemDocIndicator: removeSystemDocIndicatorForShell,&lt;br /&gt;
        refreshSystemDocSourceViewer: renderSystemDocSourceViewerForShell&lt;br /&gt;
    };&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
function loadLangScript(done) {&lt;br /&gt;
    $.getScript(&#039;/index.php?title=미디어위키:Lang.js&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;)&lt;br /&gt;
        .done(function() {&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        })&lt;br /&gt;
        .fail(function(a, b, c) {&lt;br /&gt;
            console.error(&#039;Lang.js load failed:&#039;, b, c);&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initHalftoneBackground() {&lt;br /&gt;
    try {&lt;br /&gt;
        initWebGLHalftoneBackground();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;WebGL halftone background failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initWebGLHalftoneBackground() {&lt;br /&gt;
    var canvasId = &#039;site-halftone-bg&#039;;&lt;br /&gt;
    var existing = document.getElementById(canvasId);&lt;br /&gt;
    var canvas = existing || document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    var halftoneState = window.SiteHalftoneBackgroundState || (window.SiteHalftoneBackgroundState = { runId: 0 });&lt;br /&gt;
    var runId = halftoneState.runId + 1;&lt;br /&gt;
&lt;br /&gt;
    halftoneState.runId = runId;&lt;br /&gt;
&lt;br /&gt;
    if (!existing) {&lt;br /&gt;
        canvas.id = canvasId;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        document.body.insertBefore(canvas, document.body.firstChild || null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canvas.style.position = &#039;fixed&#039;;&lt;br /&gt;
    canvas.style.inset = &#039;0&#039;;&lt;br /&gt;
    canvas.style.width = &#039;100vw&#039;;&lt;br /&gt;
    canvas.style.height = &#039;100vh&#039;;&lt;br /&gt;
    canvas.style.pointerEvents = &#039;none&#039;;&lt;br /&gt;
    canvas.style.background = &#039;#000000&#039;;&lt;br /&gt;
    canvas.style.display = &#039;block&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!canvas.getAttribute(&#039;data-halftone-context-watch&#039;)) {&lt;br /&gt;
        canvas.setAttribute(&#039;data-halftone-context-watch&#039;, &#039;1&#039;);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;, function (event) {&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            canvas.style.display = &#039;none&#039;;&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = true;&lt;br /&gt;
                window.SiteHalftoneBackgroundState.runId += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }, false);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextrestored&#039;, function () {&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = false;&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(initWebGLHalftoneBackground, 0);&lt;br /&gt;
        }, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gl = canvas.getContext(&#039;webgl&#039;, {&lt;br /&gt;
        alpha: false,&lt;br /&gt;
        antialias: false,&lt;br /&gt;
        depth: false,&lt;br /&gt;
        stencil: false,&lt;br /&gt;
        preserveDrawingBuffer: false,&lt;br /&gt;
        powerPreference: &#039;low-power&#039;&lt;br /&gt;
    }) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!gl) {&lt;br /&gt;
        canvas.style.display = &#039;none&#039;;&lt;br /&gt;
        console.warn(&#039;WebGL background unavailable.&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexSrc = [&lt;br /&gt;
        &#039;attribute vec2 a_position;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_position, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var fragmentSrc = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_resolution;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;const float TAU = 6.28318530718;&#039;,&lt;br /&gt;
        &#039;float gaussian(float v, float r) {&#039;,&lt;br /&gt;
        &#039;  return exp(-((v * v) / max(0.0001, r * r)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float hash(vec2 p) {&#039;,&lt;br /&gt;
        &#039;  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float bucketAlpha(float a) {&#039;,&lt;br /&gt;
        &#039;  float i = floor(a * 9.0);&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 1.0) return 0.040;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 2.0) return 0.080;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 3.0) return 0.135;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 4.0) return 0.210;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 5.0) return 0.310;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 6.0) return 0.430;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 7.0) return 0.580;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 8.0) return 0.760;&#039;,&lt;br /&gt;
        &#039;  return 0.920;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 frag = gl_FragCoord.xy;&#039;,&lt;br /&gt;
        &#039;  float spacing = 5.0;&#039;,&lt;br /&gt;
        &#039;  float dotSize = 1.08;&#039;,&lt;br /&gt;
        &#039;  vec2 grid = floor(frag / spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 inCell = mod(frag, spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 dotOrigin = vec2(1.0, 1.0);&#039;,&lt;br /&gt;
        &#039;  vec2 dotCenter = dotOrigin + vec2(dotSize * 0.5);&#039;,&lt;br /&gt;
        &#039;  vec2 local = abs(inCell - dotCenter);&#039;,&lt;br /&gt;
        &#039;  float noise = hash(grid);&#039;,&lt;br /&gt;
        &#039;  float size = dotSize + noise * 0.18;&#039;,&lt;br /&gt;
        &#039;  float dotMask = 1.0 - smoothstep(size * 0.5, size * 0.5 + 0.22, max(local.x, local.y));&#039;,&lt;br /&gt;
        &#039;  vec2 uv = frag / u_resolution;&#039;,&lt;br /&gt;
        &#039;  float centerLine = 0.50 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 1.32 + 0.08) * TAU) * 0.070 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 3.18 + 0.34) * TAU) * 0.030;&#039;,&lt;br /&gt;
        &#039;  float u = uv.x - centerLine;&#039;,&lt;br /&gt;
        &#039;  float absU = abs(u);&#039;,&lt;br /&gt;
        &#039;  float sideLift = smoothstep(0.065, 0.44, absU);&#039;,&lt;br /&gt;
        &#039;  float valley = gaussian(u, 0.150);&#039;,&lt;br /&gt;
        &#039;  float t = u_time;&#039;,&lt;br /&gt;
        &#039;  float leftRibbonCenter = -0.28 + sin((uv.y * 3.20 + 0.12) * TAU) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float rightRibbonCenter = 0.27 + sin((uv.y * 2.85 + 0.56) * TAU) * 0.055;&#039;,&lt;br /&gt;
        &#039;  float leftRibbon = gaussian(u - leftRibbonCenter, 0.105);&#039;,&lt;br /&gt;
        &#039;  float rightRibbon = gaussian(u - rightRibbonCenter, 0.110);&#039;,&lt;br /&gt;
        &#039;  float foldedU = u +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 4.40 + 0.22) * TAU) * 0.050 * (0.3 + sideLift) +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 7.20 + uv.x * 1.10) * TAU) * 0.022;&#039;,&lt;br /&gt;
        &#039;  float verticalFold = pow(0.5 + 0.5 * cos(((foldedU * 3.05) + (sin(uv.y * TAU * 2.35) * 0.18)) * TAU), 2.5);&#039;,&lt;br /&gt;
        &#039;  float diagonalFold = pow(0.5 + 0.5 * cos(((foldedU * 1.80) - (uv.y * 1.12) + 0.18) * TAU), 2.1);&#039;,&lt;br /&gt;
        &#039;  float waist = gaussian(uv.y - 0.50, 0.25) * gaussian(absU - 0.20, 0.19);&#039;,&lt;br /&gt;
        &#039;  float grain = (noise - 0.5) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float staticField =&#039;,&lt;br /&gt;
        &#039;    0.055 +&#039;,&lt;br /&gt;
        &#039;    sideLift * 0.210 +&#039;,&lt;br /&gt;
        &#039;    (leftRibbon + rightRibbon) * 0.145 +&#039;,&lt;br /&gt;
        &#039;    verticalFold * (0.055 + sideLift * 0.115) +&#039;,&lt;br /&gt;
        &#039;    diagonalFold * 0.045 +&#039;,&lt;br /&gt;
        &#039;    waist * 0.060 -&#039;,&lt;br /&gt;
        &#039;    valley * 0.150 +&#039;,&lt;br /&gt;
        &#039;    grain;&#039;,&lt;br /&gt;
        &#039;  float alpha = staticField;&#039;,&lt;br /&gt;
        &#039;  alpha += 0.115 * (leftRibbon + rightRibbon) * sin(t * 0.00030 + ((uv.y * 1.9) + sideLift * 0.4) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.095 * verticalFold * (0.4 + sideLift) * sin(t * 0.00041 + ((uv.y * 2.7) + foldedU * 0.65) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.070 * waist * sin(t * 0.00053 + ((uv.y * 3.1) - absU * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.060 * (1.0 - valley) * diagonalFold * sin(t * 0.00067 + ((uv.y * 1.4) + uv.x * 0.6) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.038 * (0.35 + sideLift) * (0.35 + noise) * sin(t * 0.00079 + ((uv.y * 4.6) + noise * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha = bucketAlpha(clamp(alpha, 0.025, 0.96));&#039;,&lt;br /&gt;
        &#039;  float value = alpha * dotMask;&#039;,&lt;br /&gt;
        &#039;  gl_FragColor = vec4(vec3(0.8862745 * value), 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function compileShader(type, source) {&lt;br /&gt;
        var shader = gl.createShader(type);&lt;br /&gt;
        gl.shaderSource(shader, source);&lt;br /&gt;
        gl.compileShader(shader);&lt;br /&gt;
&lt;br /&gt;
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {&lt;br /&gt;
            console.error(&#039;WebGL shader compile error:&#039;, gl.getShaderInfoLog(shader));&lt;br /&gt;
            gl.deleteShader(shader);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return shader;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexShader = compileShader(gl.VERTEX_SHADER, vertexSrc);&lt;br /&gt;
    var fragmentShader = compileShader(gl.FRAGMENT_SHADER, fragmentSrc);&lt;br /&gt;
&lt;br /&gt;
    if (!vertexShader || !fragmentShader) return;&lt;br /&gt;
&lt;br /&gt;
    var program = gl.createProgram();&lt;br /&gt;
    gl.attachShader(program, vertexShader);&lt;br /&gt;
    gl.attachShader(program, fragmentShader);&lt;br /&gt;
    gl.linkProgram(program);&lt;br /&gt;
&lt;br /&gt;
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {&lt;br /&gt;
        console.error(&#039;WebGL program link error:&#039;, gl.getProgramInfoLog(program));&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var positionLoc = gl.getAttribLocation(program, &#039;a_position&#039;);&lt;br /&gt;
    var resolutionLoc = gl.getUniformLocation(program, &#039;u_resolution&#039;);&lt;br /&gt;
    var timeLoc = gl.getUniformLocation(program, &#039;u_time&#039;);&lt;br /&gt;
&lt;br /&gt;
    var buffer = gl.createBuffer();&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([&lt;br /&gt;
        -1, -1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
         1,  1&lt;br /&gt;
    ]), gl.STATIC_DRAW);&lt;br /&gt;
&lt;br /&gt;
    gl.useProgram(program);&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.enableVertexAttribArray(positionLoc);&lt;br /&gt;
    gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
    function resize() {&lt;br /&gt;
        var dpr = Math.min(window.devicePixelRatio || 1, 1.5);&lt;br /&gt;
        var cssW = Math.max(1, window.innerWidth || document.documentElement.clientWidth || 1);&lt;br /&gt;
        var cssH = Math.max(1, window.innerHeight || document.documentElement.clientHeight || 1);&lt;br /&gt;
        var w = Math.max(1, Math.floor(cssW * dpr));&lt;br /&gt;
        var h = Math.max(1, Math.floor(cssH * dpr));&lt;br /&gt;
&lt;br /&gt;
        if (canvas.width !== w || canvas.height !== h) {&lt;br /&gt;
            canvas.width = w;&lt;br /&gt;
            canvas.height = h;&lt;br /&gt;
            canvas.style.width = cssW + &#039;px&#039;;&lt;br /&gt;
            canvas.style.height = cssH + &#039;px&#039;;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var prefersReducedMotion = false;&lt;br /&gt;
    try {&lt;br /&gt;
        prefersReducedMotion = window.matchMedia &amp;amp;&amp;amp; window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches;&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneNode() {&lt;br /&gt;
        return document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneState() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            return &#039;none&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) {&lt;br /&gt;
            return &#039;active&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundBusy === &#039;function&#039; &amp;amp;&amp;amp; instance.isHalftoneBackgroundBusy()) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;present&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function shouldSkipHalftoneDrawForGlobe() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) return false;&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Background pause/resume — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * While the nations globe is being held, keep the last rendered halftone&lt;br /&gt;
         * frame on screen and pause the halftone clock.  The important detail is&lt;br /&gt;
         * that this is not a wall-clock catch-up: skipped seconds are not replayed&lt;br /&gt;
         * or fast-forwarded when the pointer is released.&lt;br /&gt;
         */&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) return true;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; instance.dragging) return true;&lt;br /&gt;
&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundHardBusy === &#039;function&#039;) {&lt;br /&gt;
            return instance.isHalftoneBackgroundHardBusy();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) return true;&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFrameInterval() {&lt;br /&gt;
        if (prefersReducedMotion) {&lt;br /&gt;
            return 1000;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Nations background parity — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * The nations page used to permanently lower the halftone cadence while&lt;br /&gt;
         * the globe existed.  Profiling showed that the steady halftone draw is&lt;br /&gt;
         * normally cheap; the real contention happens during globe drag, first&lt;br /&gt;
         * WebGL texture upload, and long layout/render tasks.  Keep the same&lt;br /&gt;
         * full-quality cadence as ordinary pages, and skip only the frames that&lt;br /&gt;
         * would directly collide with an active/busy globe moment.&lt;br /&gt;
         */&lt;br /&gt;
        return 66;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var lastFrame = 0;&lt;br /&gt;
    var startTime = performance.now();&lt;br /&gt;
    var visualTime = 0;&lt;br /&gt;
    var lastVisualNow = 0;&lt;br /&gt;
&lt;br /&gt;
    function draw(now) {&lt;br /&gt;
        var state = getNationsGlobeHalftoneState();&lt;br /&gt;
        var interval = getFrameInterval();&lt;br /&gt;
        var perf = window.InteractionPerf;&lt;br /&gt;
        var delta;&lt;br /&gt;
&lt;br /&gt;
        if (!lastVisualNow) {&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
        }&lt;br /&gt;
        delta = Math.max(0, Math.min(120, now - lastVisualNow));&lt;br /&gt;
        lastVisualNow = now;&lt;br /&gt;
        visualTime += delta;&lt;br /&gt;
&lt;br /&gt;
        function runDraw() {&lt;br /&gt;
            resize();&lt;br /&gt;
&lt;br /&gt;
            gl.clearColor(0, 0, 0, 1);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.uniform2f(resolutionLoc, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform1f(timeLoc, visualTime);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLES, 0, 6);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (perf &amp;amp;&amp;amp; typeof perf.measureSync === &#039;function&#039;) {&lt;br /&gt;
            return perf.measureSync(&#039;background halftone draw&#039;, { globeState: state, interval: interval }, runDraw);&lt;br /&gt;
        }&lt;br /&gt;
        return runDraw();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(now) {&lt;br /&gt;
        if (halftoneState.runId !== runId || halftoneState.contextLost) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(render);&lt;br /&gt;
&lt;br /&gt;
        if (document.hidden) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (now - lastFrame &amp;lt; getFrameInterval()) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (shouldSkipHalftoneDrawForGlobe()) {&lt;br /&gt;
            lastFrame = now;&lt;br /&gt;
            halftoneState.lastGlobeBusySkipAt = now;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Pause the halftone clock while a hard-busy globe frame owns the&lt;br /&gt;
             * budget.  Without this, the next visible background frame catches&lt;br /&gt;
             * up to wall-clock time and the pattern appears to skip forward.&lt;br /&gt;
             * Updating lastVisualNow here makes the animation resume from the&lt;br /&gt;
             * last visible moment instead of replaying the hidden gap.&lt;br /&gt;
             */&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        draw(now);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    draw(performance.now());&lt;br /&gt;
    requestAnimationFrame(render);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SVG_BELL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_BELL_DOT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell-dot&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M11.68 2.009A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673c-.824-.85-1.678-1.731-2.21-3.348&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;18&amp;quot; cy=&amp;quot;5&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LIST = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-list&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 5h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 12h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 19h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 5h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 12h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 19h13&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LANGUAGES = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-languages&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;m5 8 6 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m4 14 6-6 2-3&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M2 5h12&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 2h1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m22 22-5-10-5 10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 18h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_POWER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-power&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 2v10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.4 6.6a9 9 0 1 1-12.77.04&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SETTINGS = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-settings&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_TEXT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-text&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 8h8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 12h10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 16h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_EYE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-eye&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_NEWSPAPER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-newspaper&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M15 18h-5&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 14h-8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-4 0v-9a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;8&amp;quot; height=&amp;quot;4&amp;quot; x=&amp;quot;10&amp;quot; y=&amp;quot;6&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_GREAT_WALL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-great-wall lucide lucide-paint-roller&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;rect width=&amp;quot;16&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;2&amp;quot; y=&amp;quot;2&amp;quot; rx=&amp;quot;2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M10 16v-2a2 2 0 0 1 2-2h8a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;4&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;8&amp;quot; y=&amp;quot;16&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_TROPHY = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-trophy&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 9h1.5a1 1 0 0 0 0-5H18&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9H4.5a1 1 0 0 1 0-5H6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_PACKAGE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-package&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 3v6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M16.76 3a2 2 0 0 1 1.8 1.1l2.23 4.479a2 2 0 0 1 .21.891V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9.472a2 2 0 0 1 .211-.894L5.45 4.1A2 2 0 0 1 7.24 3z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.054 9.013h17.893&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
var PROFILE_RENDER_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function invalidateProfileRender() {&lt;br /&gt;
    PROFILE_RENDER_TOKEN++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    initHalftoneBackground();&lt;br /&gt;
&lt;br /&gt;
// ── 하단 Plank 단축키 가이드 ──&lt;br /&gt;
function escapeClbiBottomGuideHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiBottomGuidePageName() {&lt;br /&gt;
    var pageName = window.mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) {&lt;br /&gt;
        pageName = window.location.pathname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return String(pageName)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsShortcutContext() {&lt;br /&gt;
    var pageName = readClbiBottomGuidePageName();&lt;br /&gt;
&lt;br /&gt;
    return pageName === &#039;시대&#039; ||&lt;br /&gt;
        pageName === &#039;Era&#039; ||&lt;br /&gt;
        !!document.querySelector(&#039;.clbi-nations-panel-stack, .clbi-nations-globe-window, .clbi-nations-tabpanel&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiWikiEditShortcutContext() {&lt;br /&gt;
    var action = window.mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgAction&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    var search = String(window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
    return action === &#039;edit&#039; ||&lt;br /&gt;
        action === &#039;submit&#039; ||&lt;br /&gt;
        /(?:^|[?&amp;amp;])action=(?:edit|submit)(?:&amp;amp;|$)/.test(search) ||&lt;br /&gt;
        !!document.querySelector(&#039;#editform, #wpSave, input[name=&amp;quot;wpSave&amp;quot;], button[name=&amp;quot;wpSave&amp;quot;]&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function isClbiNationListManagerShortcutContext() {&lt;br /&gt;
    return !!document.querySelector(&#039;.nation-list-manager&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiBottomShortcutItems() {&lt;br /&gt;
    if (isClbiWikiEditShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;변경사항 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationListManagerShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;국가 목록 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Q&#039;, label: &#039;이전 시대&#039; },&lt;br /&gt;
            { key: &#039;E&#039;, label: &#039;다음 시대&#039; },&lt;br /&gt;
            { key: &#039;Shift+Q&#039;, label: &#039;이전 연도&#039; },&lt;br /&gt;
            { key: &#039;Shift+E&#039;, label: &#039;다음 연도&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+Q&#039;, label: &#039;이전 대륙&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+E&#039;, label: &#039;다음 대륙&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return [];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderClbiBottomShortcutGuide() {&lt;br /&gt;
    var guide = document.getElementById(&#039;clbi-bottom-shortcut-guide&#039;);&lt;br /&gt;
    var items;&lt;br /&gt;
    var html;&lt;br /&gt;
&lt;br /&gt;
    if (!guide) return;&lt;br /&gt;
&lt;br /&gt;
    items = getClbiBottomShortcutItems();&lt;br /&gt;
&lt;br /&gt;
    if (!items.length) {&lt;br /&gt;
        guide.classList.add(&#039;is-empty&#039;);&lt;br /&gt;
        guide.innerHTML = &#039;&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    guide.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    html = &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-list&amp;quot;&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        html += &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-key&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.key) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-label&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    html += &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    guide.innerHTML = html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiBottomPlankHtml(wrapId, navId, mainId) {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-bottom-shortcut-guide&amp;quot; class=&amp;quot;is-empty&amp;quot; aria-label=&amp;quot;단축키 안내&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
&lt;br /&gt;
function getClbiNationsTabAtPointer(tabpanel) {&lt;br /&gt;
    var element;&lt;br /&gt;
    var tab;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return null;&lt;br /&gt;
    if (CLBI_NATIONS_LAST_POINTER_X === null || CLBI_NATIONS_LAST_POINTER_Y === null) return null;&lt;br /&gt;
    if (typeof document.elementFromPoint !== &#039;function&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
    element = document.elementFromPoint(CLBI_NATIONS_LAST_POINTER_X, CLBI_NATIONS_LAST_POINTER_Y);&lt;br /&gt;
    if (!element) return null;&lt;br /&gt;
&lt;br /&gt;
    tab = element.closest ? element.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (!tab || !tabpanel.contains(tab)) return null;&lt;br /&gt;
&lt;br /&gt;
    return tab;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateClbiNationsPointerHover(tabpanel, forceSuppress) {&lt;br /&gt;
    var tab = getClbiNationsTabAtPointer(tabpanel);&lt;br /&gt;
    var suppressContinent;&lt;br /&gt;
    var tabContinent;&lt;br /&gt;
    var tabIsActive;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
&lt;br /&gt;
    if (!tab) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabContinent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    tabIsActive = tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (tabIsActive) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    suppressContinent = tabpanel.getAttribute(&#039;data-clbi-hover-suppress-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (forceSuppress || !suppressContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-hover-suppress-continent&#039;, tabContinent);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (suppressContinent === tabContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * The pointer actually moved onto another tab after the keyboard switch.&lt;br /&gt;
     * At that point this is no longer stale browser :hover; let normal hover work.&lt;br /&gt;
     */&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateAllClbiNationsPointerHovers() {&lt;br /&gt;
    var panels = document.querySelectorAll(&#039;.clbi-nations-tabpanel.is-keyboard-switching&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        if (isClbiNationsPanelOwnedTabpanel(tabpanel)) return;&lt;br /&gt;
        validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsPanelOwnedTabpanel(tabpanel) {&lt;br /&gt;
    /*&lt;br /&gt;
     * Mouse continent tab regression guard.&lt;br /&gt;
     * -------------------------------------&lt;br /&gt;
     * Detached SPA preparation serializes attributes/classes but it cannot&lt;br /&gt;
     * serialize DOM event listeners or JS properties.  The NationsPanel fix&lt;br /&gt;
     * therefore uses the DOM property CLBI_NationsPanelOwned /&lt;br /&gt;
     * CLBI_NationsTabPanelBound as the real ownership marker.&lt;br /&gt;
     *&lt;br /&gt;
     * Do NOT treat data-nations-tabpanel-ready or clbi-nations-continent-cache&lt;br /&gt;
     * as ownership here.  Those can survive innerHTML insertion while the real&lt;br /&gt;
     * click listeners were lost, causing Common.js to delegate to a panel that&lt;br /&gt;
     * NationsPanel has not rebound yet.  The fallback below must remain able to&lt;br /&gt;
     * handle mouse clicks until NationsPanel reclaims the live DOM node.&lt;br /&gt;
     */&lt;br /&gt;
    return !!(tabpanel &amp;amp;&amp;amp; tabpanel.CLBI_NationsPanelOwned);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsLiveContinentActive(tabpanel, continent) {&lt;br /&gt;
    var tab;&lt;br /&gt;
    var panel;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !continent) return false;&lt;br /&gt;
&lt;br /&gt;
    tab = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panel = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent-panel&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return !!(&lt;br /&gt;
        tab &amp;amp;&amp;amp;&lt;br /&gt;
        panel &amp;amp;&amp;amp;&lt;br /&gt;
        (tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.classList.contains(&#039;is-active&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.getAttribute(&#039;aria-hidden&#039;) !== &#039;true&#039; &amp;amp;&amp;amp;&lt;br /&gt;
        !panel.hasAttribute(&#039;hidden&#039;)&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function activateClbiNationsContinent(tabpanel, targetContinent, options) {&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var panels;&lt;br /&gt;
    var skipOwner = !!(options &amp;amp;&amp;amp; options.skipOwner);&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !targetContinent) return false;&lt;br /&gt;
&lt;br /&gt;
    if (!skipOwner &amp;amp;&amp;amp; isClbiNationsPanelOwnedTabpanel(tabpanel)) {&lt;br /&gt;
        var owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
&lt;br /&gt;
        if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (owner.activateContinent(targetContinent, {&lt;br /&gt;
                    source: &#039;common-legacy-click&#039;,&lt;br /&gt;
                    panel: tabpanel&lt;br /&gt;
                })) {&lt;br /&gt;
                    return true;&lt;br /&gt;
                }&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Ownership markers can be stale during SPA/hydration edge cases.  If&lt;br /&gt;
         * the owner API is missing or refuses the live panel, do not drop the&lt;br /&gt;
         * mouse click.  Fall through to the local fallback so pointer users are&lt;br /&gt;
         * never left with keyboard-only continent tabs.&lt;br /&gt;
         */&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    panels = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
    if (!tabs.length || !panels.length) return false;&lt;br /&gt;
&lt;br /&gt;
    tabs.forEach(function (tab) {&lt;br /&gt;
        var active = tab.getAttribute(&#039;data-continent&#039;) === targetContinent;&lt;br /&gt;
        tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
        tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panels.forEach(function (panel) {&lt;br /&gt;
        var active = panel.getAttribute(&#039;data-continent-panel&#039;) === targetContinent;&lt;br /&gt;
        panel.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
        if (active) {&lt;br /&gt;
            panel.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            panel.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.sync === &#039;function&#039;) window.CLBI_DECORATIONS.sync();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    validateClbiNationsPointerHover(tabpanel, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
    tabpanel.classList.remove(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
    tabpanel.removeAttribute(&#039;data-clbi-hover-suppress-continent&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveClbiNationsContinent(direction) {&lt;br /&gt;
    var tabpanel = document.querySelector(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var activeIndex;&lt;br /&gt;
    var nextIndex;&lt;br /&gt;
    var target;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return false;&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsPanelOwnedTabpanel(tabpanel) &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI_NATIONS_PANEL &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI_NATIONS_PANEL.moveContinent === &#039;function&#039;) {&lt;br /&gt;
        return !!window.CLBI_NATIONS_PANEL.moveContinent(direction, {&lt;br /&gt;
            source: &#039;common-legacy-keyboard&#039;,&lt;br /&gt;
            panel: tabpanel&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
    activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
        return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
    nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
    target = tabs[nextIndex].getAttribute(&#039;data-continent&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (activateClbiNationsContinent(tabpanel, target)) {&lt;br /&gt;
        setClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
        }, 0);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsTabpanelControls(root) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var panels = scope.querySelectorAll(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        /*&lt;br /&gt;
         * Common.js is only a safety fallback for continent tabs; NationsPanel&lt;br /&gt;
         * is the primary owner.  This guard must be a DOM property, not a&lt;br /&gt;
         * serialized attribute.  SPA warm prepaint can preserve&lt;br /&gt;
         * data-clbi-nations-tabs-ready=&amp;quot;1&amp;quot; while losing the actual event&lt;br /&gt;
         * listeners, which was the root cause of mouse clicks no longer moving&lt;br /&gt;
         * continents while keyboard shortcuts still worked.&lt;br /&gt;
         */&lt;br /&gt;
        if (tabpanel.CLBI_NationsCommonTabsBound) return;&lt;br /&gt;
        tabpanel.CLBI_NationsCommonTabsBound = true;&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-nations-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerdown&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
            if (activateClbiNationsContinent(tabpanel, tab.getAttribute(&#039;data-continent&#039;))) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
            var handled = false;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsContinent(-1);&lt;br /&gt;
            else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsContinent(1);&lt;br /&gt;
&lt;br /&gt;
            if (handled) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                event.stopPropagation();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function handleClbiNationsContinentDocumentClick(event) {&lt;br /&gt;
    var target;&lt;br /&gt;
    var tab;&lt;br /&gt;
    var tabpanel;&lt;br /&gt;
    var continent;&lt;br /&gt;
    var owner;&lt;br /&gt;
    var handled = false;&lt;br /&gt;
&lt;br /&gt;
    if (!event || event.__clbiNationsContinentHandled) return;&lt;br /&gt;
    target = event.target;&lt;br /&gt;
    if (!target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
    tab = target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;);&lt;br /&gt;
    if (!tab) return;&lt;br /&gt;
&lt;br /&gt;
    tabpanel = tab.closest ? tab.closest(&#039;.clbi-nations-tabpanel&#039;) : null;&lt;br /&gt;
    if (!tabpanel || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
    continent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    if (!continent) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Final live-DOM mouse delegate.&lt;br /&gt;
     * --------------------------------&lt;br /&gt;
     * The continent tab UI has two initialization phases: detached preparation&lt;br /&gt;
     * for seamless SPA entry, then live DOM hydration.  Detached preparation can&lt;br /&gt;
     * leave serialized &amp;quot;ready&amp;quot; attributes behind while losing event listeners.&lt;br /&gt;
     * Keyboard shortcuts still work because they call the public NationsPanel&lt;br /&gt;
     * API directly, but mouse clicks depend on live listeners.  This capturing&lt;br /&gt;
     * delegate is intentionally independent of per-panel ready attributes: it&lt;br /&gt;
     * always resolves the clicked live tab and hands it to NationsPanel first,&lt;br /&gt;
     * then falls back to the local Common.js switcher if needed.&lt;br /&gt;
     */&lt;br /&gt;
    event.__clbiNationsContinentHandled = true;&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
    owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
    if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
        try {&lt;br /&gt;
            handled = !!owner.activateContinent(continent, {&lt;br /&gt;
                source: &#039;mouse-click-live-delegate&#039;,&lt;br /&gt;
                panel: tabpanel&lt;br /&gt;
            });&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Owner API stale-cache guard.&lt;br /&gt;
         *&lt;br /&gt;
         * A previous regression came from NationsPanel returning true after it&lt;br /&gt;
         * updated cached/detached tab nodes instead of the clicked live tab. A&lt;br /&gt;
         * true return value alone is therefore not enough for mouse input. Verify&lt;br /&gt;
         * the live DOM that received the click. If it did not become active,&lt;br /&gt;
         * bypass the owner and run the Common.js live query fallback directly.&lt;br /&gt;
         */&lt;br /&gt;
        if (handled &amp;amp;&amp;amp; !isClbiNationsLiveContinentActive(tabpanel, continent)) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!handled) {&lt;br /&gt;
        handled = activateClbiNationsContinent(tabpanel, continent, { skipOwner: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (handled) {&lt;br /&gt;
        try { playStaticSound(); } catch (err2) {}&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) event.stopImmediatePropagation();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsGlobalClickDelegate() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-global-click-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-global-click-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleClbiNationsContinentDocumentClick, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsPointerHoverValidation() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointermove&#039;, function (event) {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = event.clientX;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = event.clientY;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiBottomShortcutSystem(root) {&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsGlobalClickDelegate();&lt;br /&gt;
    initClbiNationsTabpanelControls(root || document);&lt;br /&gt;
    initClbiNationsPointerHoverValidation();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// ── 상·하단 네비게이션 바 ──&lt;br /&gt;
function buildClbiNavHtml(position) {&lt;br /&gt;
    var isBottom = position === &#039;bottom&#039;;&lt;br /&gt;
    var base = isBottom ? &#039;clbi-bottom&#039; : &#039;clbi-top&#039;;&lt;br /&gt;
    var shortBase = isBottom ? &#039;clbi-bnav&#039; : &#039;clbi-tnav&#039;;&lt;br /&gt;
    var wrapId = base + &#039;-nav-wrap&#039;;&lt;br /&gt;
    var navId = base + &#039;-nav&#039;;&lt;br /&gt;
    var mainId = base + &#039;-nav-main&#039;;&lt;br /&gt;
    var tabsId = base + &#039;-nav-tabs&#039;;&lt;br /&gt;
    var searchId = base + &#039;-nav-search&#039;;&lt;br /&gt;
    var inputId = isBottom ? &#039;clbi-bottom-search-input&#039; : &#039;clbi-top-search-input&#039;;&lt;br /&gt;
    var worldId = shortBase + &#039;-worldbuilding&#039;;&lt;br /&gt;
    var subId = isBottom ? &#039;clbi-bottom-sub-worldbuilding&#039; : &#039;clbi-sub-worldbuilding&#039;;&lt;br /&gt;
    var subInnerId = subId + &#039;-inner&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (isBottom) {&lt;br /&gt;
        return buildClbiBottomPlankHtml(wrapId, navId, mainId);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + tabsId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/대문&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-main-menu-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;메인 메뉴&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-project-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;프로젝트&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-top-nav-item&amp;quot; id=&amp;quot;&#039; + worldId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-worldbuilding-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;세계관&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-arrow&amp;quot;&amp;gt;▾&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    (isBottom ? &#039;&#039; : (&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;&#039; + searchId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;&#039; + inputId + &#039;&amp;quot; placeholder=&amp;quot;검색...&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                    )) +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + subId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + subInnerId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-tnav-sub-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/시대&amp;quot;&amp;gt;시대&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/설정&amp;quot;&amp;gt;설정&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/기업_및_공동체&amp;quot;&amp;gt;기업 및 공동체&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/군_정치집단&amp;quot;&amp;gt;군, 정치집단&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/인물&amp;quot;&amp;gt;인물&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeClbiShellDomOrder() {&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var canvas = document.getElementById(&#039;site-halftone-bg&#039;);&lt;br /&gt;
    var anchor;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var topRect;&lt;br /&gt;
    var wrapperRect;&lt;br /&gt;
    var needsRecovery;&lt;br /&gt;
    var expectedWrapperTop;&lt;br /&gt;
    var host;&lt;br /&gt;
&lt;br /&gt;
    if (!contentWrapper || !topNav || !bottomNav || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    CLBI shell can live inside a Liberty &amp;lt;section&amp;gt;.  Some skins/layouts give that&lt;br /&gt;
    section a flow context that lets the top nav visually overlap the content&lt;br /&gt;
    wrapper even when DOM sibling order is correct.  Mark the common parent and&lt;br /&gt;
    let Layout.css force a simple vertical flow for the shell.&lt;br /&gt;
    */&lt;br /&gt;
    host = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp;&lt;br /&gt;
        contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? contentWrapper.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (host) {&lt;br /&gt;
        host.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    viewportH = window.innerHeight || document.documentElement.clientHeight || 0;&lt;br /&gt;
    topRect = topNav.getBoundingClientRect();&lt;br /&gt;
    wrapperRect = contentWrapper.getBoundingClientRect();&lt;br /&gt;
    expectedWrapperTop = topRect.bottom + 8;&lt;br /&gt;
&lt;br /&gt;
    needsRecovery = false;&lt;br /&gt;
&lt;br /&gt;
    if (viewportH &amp;gt; 0) {&lt;br /&gt;
        if (topRect.top &amp;gt;= viewportH * 0.55) needsRecovery = true;&lt;br /&gt;
        if (wrapperRect.top &amp;gt;= viewportH * 0.60) needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (topRect.top &amp;gt; 240 || wrapperRect.top &amp;gt; 320) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (wrapperRect.top &amp;lt; expectedWrapperTop - 1) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!needsRecovery) {&lt;br /&gt;
        document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    anchor = canvas &amp;amp;&amp;amp; canvas.parentNode === document.body&lt;br /&gt;
        ? canvas.nextSibling&lt;br /&gt;
        : document.body.firstChild;&lt;br /&gt;
&lt;br /&gt;
    document.body.insertBefore(topNav, anchor);&lt;br /&gt;
    document.body.insertBefore(contentWrapper, topNav.nextSibling);&lt;br /&gt;
    document.body.insertBefore(bottomNav, contentWrapper.nextSibling);&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.normalizeClbiShellDomOrder = normalizeClbiShellDomOrder;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureClbiVerticalScaleHost() {&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var host;&lt;br /&gt;
    var existing;&lt;br /&gt;
    var parent;&lt;br /&gt;
&lt;br /&gt;
    if (!topNav || !contentWrapper || !bottomNav || !document.body) return null;&lt;br /&gt;
&lt;br /&gt;
    existing = document.getElementById(&#039;clbi-shell-scale-host&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (existing &amp;amp;&amp;amp; existing.contains(topNav) &amp;amp;&amp;amp; existing.contains(contentWrapper) &amp;amp;&amp;amp; existing.contains(bottomNav)) {&lt;br /&gt;
        existing.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        return existing;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    parent = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp; contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? topNav.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (parent &amp;amp;&amp;amp; parent !== document.body) {&lt;br /&gt;
        parent.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        parent.id = parent.id || &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
        return parent;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host = existing || document.createElement(&#039;div&#039;);&lt;br /&gt;
    host.id = &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
    host.className = &#039;clbi-shell-host&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!host.parentNode) {&lt;br /&gt;
        document.body.insertBefore(host, topNav);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host.appendChild(topNav);&lt;br /&gt;
    host.appendChild(contentWrapper);&lt;br /&gt;
    host.appendChild(bottomNav);&lt;br /&gt;
&lt;br /&gt;
    return host;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiRootPx(name, fallback) {&lt;br /&gt;
    var raw = &#039;&#039;;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        raw = getComputedStyle(document.documentElement).getPropertyValue(name);&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    value = parseFloat(raw);&lt;br /&gt;
    return isFinite(value) &amp;amp;&amp;amp; value &amp;gt; 0 ? value : fallback;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiShellVerticalScale() {&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    var host;&lt;br /&gt;
    var topInner;&lt;br /&gt;
    var bottomInner;&lt;br /&gt;
    var thresholdH;&lt;br /&gt;
    var gap;&lt;br /&gt;
    var outerGapTotal;&lt;br /&gt;
    var baseStageH;&lt;br /&gt;
    var stageW;&lt;br /&gt;
    var stageH;&lt;br /&gt;
    var viewportW;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var availableW;&lt;br /&gt;
    var availableH;&lt;br /&gt;
    var topH;&lt;br /&gt;
    var bottomH;&lt;br /&gt;
    var contentH;&lt;br /&gt;
    var widthScale;&lt;br /&gt;
    var heightScale;&lt;br /&gt;
    var scale;&lt;br /&gt;
    var shouldScale;&lt;br /&gt;
&lt;br /&gt;
    if (!root || !body) return;&lt;br /&gt;
&lt;br /&gt;
    host = ensureClbiVerticalScaleHost();&lt;br /&gt;
    if (!host) return;&lt;br /&gt;
&lt;br /&gt;
    thresholdH = readClbiRootPx(&#039;--clbi-vertical-scale-threshold-h&#039;, 1080);&lt;br /&gt;
    gap = readClbiRootPx(&#039;--layout-gap&#039;, 8);&lt;br /&gt;
    outerGapTotal = gap * 2;&lt;br /&gt;
    baseStageH = Math.max(420, thresholdH - outerGapTotal);&lt;br /&gt;
    stageW = readClbiRootPx(&#039;--layout-shell-w&#039;, 1880);&lt;br /&gt;
&lt;br /&gt;
    viewportW = Math.max(320, window.innerWidth || document.documentElement.clientWidth || stageW);&lt;br /&gt;
    viewportH = Math.max(320, window.innerHeight || document.documentElement.clientHeight || thresholdH);&lt;br /&gt;
    availableW = Math.max(240, viewportW - outerGapTotal);&lt;br /&gt;
    availableH = Math.max(240, viewportH - outerGapTotal);&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    평상시에는 기존 세로 채움 레이아웃을 기준으로 삼는다.&lt;br /&gt;
    세로가 기준점보다 작아지는 경우에는 기준 높이를 고정점으로 삼고,&lt;br /&gt;
    가로만 부족한 경우에는 현재 사용 가능한 세로 높이를 고정점으로 삼는다.&lt;br /&gt;
    이렇게 해야 가로 부족으로 scale에 들어갈 때 본문 높이가 갑자기 접히지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    stageH = availableH &amp;gt;= baseStageH ? availableH : baseStageH;&lt;br /&gt;
&lt;br /&gt;
    topInner = document.getElementById(&#039;clbi-top-nav&#039;);&lt;br /&gt;
    bottomInner = document.getElementById(&#039;clbi-bottom-nav&#039;);&lt;br /&gt;
&lt;br /&gt;
    topH = topInner ? Math.ceil(topInner.offsetHeight || topInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    bottomH = bottomInner ? Math.ceil(bottomInner.offsetHeight || bottomInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    contentH = Math.max(360, Math.floor(stageH - topH - bottomH - (gap * 2)));&lt;br /&gt;
&lt;br /&gt;
    widthScale = availableW &amp;lt; stageW ? availableW / stageW : 1;&lt;br /&gt;
    heightScale = availableH &amp;lt; baseStageH ? availableH / baseStageH : 1;&lt;br /&gt;
    scale = Math.min(1, widthScale, heightScale);&lt;br /&gt;
    scale = Math.max(0.50, Math.min(1, Math.floor(scale * 1000) / 1000));&lt;br /&gt;
    shouldScale = scale &amp;lt; 0.999;&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-w&#039;, stageW + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-h&#039;, Math.floor(stageH) + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-content-h&#039;, contentH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-shell-scale&#039;, String(scale));&lt;br /&gt;
&lt;br /&gt;
    body.classList.toggle(&#039;clbi-shell-vertical-scale&#039;, shouldScale);&lt;br /&gt;
&lt;br /&gt;
    if (shouldScale) {&lt;br /&gt;
        resetLeftRecentAdaptiveState();&lt;br /&gt;
        resetLeftBillboardAdaptiveState();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
window.updateClbiShellVerticalScale = updateClbiShellVerticalScale;&lt;br /&gt;
&lt;br /&gt;
var $contentWrapper = $(&#039;.content-wrapper&#039;).first();&lt;br /&gt;
&lt;br /&gt;
if ($contentWrapper.length) {&lt;br /&gt;
    $(&#039;#clbi-top-nav-wrap, #clbi-bottom-nav-wrap&#039;).remove();&lt;br /&gt;
    $contentWrapper.before(buildClbiNavHtml(&#039;top&#039;));&lt;br /&gt;
    $contentWrapper.after(buildClbiNavHtml(&#039;bottom&#039;));&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsTabpanelControls(document);&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
function runClbiShellMetricsBatch() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var topH = 0;&lt;br /&gt;
    var bottomH = 0;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    if (top) {&lt;br /&gt;
        topH = Math.ceil(top.getBoundingClientRect().height || top.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (bottom) {&lt;br /&gt;
        bottomH = Math.ceil(bottom.getBoundingClientRect().height || bottom.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-top-nav-outer-h&#039;, topH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-bottom-nav-outer-h&#039;, bottomH + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (typeof updateClbiShellVerticalScale === &#039;function&#039;) {&lt;br /&gt;
        updateClbiShellVerticalScale();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestClbiShellMetricsFrame() {&lt;br /&gt;
    if (CLBI_SHELL_METRICS_RAF !== null) return;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = window.requestAnimationFrame&lt;br /&gt;
        ? window.requestAnimationFrame(runClbiShellMetricsBatch)&lt;br /&gt;
        : window.setTimeout(runClbiShellMetricsBatch, 16);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiShellMetrics() {&lt;br /&gt;
    requestClbiShellMetricsFrame();&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 0);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 80);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 240);&lt;br /&gt;
}&lt;br /&gt;
function watchClbiShellMetrics() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var observer;&lt;br /&gt;
&lt;br /&gt;
    scheduleClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
    $(window).on(&#039;resize orientationchange&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    $(window).on(&#039;pageshow.clbiShellScale focus.clbiShellScale&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
        if (!document.hidden) scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
    $(window).on(&#039;resize.clbiLeftBillboard orientationchange.clbiLeftBillboard&#039;, scheduleLeftSidebarVerticalFit);&lt;br /&gt;
    $(window).on(&#039;resize.clbiRecentViewport orientationchange.clbiRecentViewport&#039;, function () { scheduleAdaptiveLeftRecentItems(); scheduleClbiContentBottomGap(); });&lt;br /&gt;
    $(window).on(&#039;resize.clbiContentBottomGap orientationchange.clbiContentBottomGap&#039;, scheduleClbiContentBottomGap);&lt;br /&gt;
&lt;br /&gt;
    if (window.ResizeObserver) {&lt;br /&gt;
        observer = new ResizeObserver(scheduleClbiShellMetrics);&lt;br /&gt;
        if (top) observer.observe(top);&lt;br /&gt;
        if (bottom) observer.observe(bottom);&lt;br /&gt;
        window.CLBI_SHELL_RESIZE_OBSERVER = observer;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindClbiWorldbuildingToggle(buttonSelector, menuSelector) {&lt;br /&gt;
    $(buttonSelector).on(&#039;click&#039;, function() {&lt;br /&gt;
        var $menu = $(menuSelector);&lt;br /&gt;
        var $btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        $menu.toggleClass(&#039;worldbuilding-open&#039;);&lt;br /&gt;
        $btn.toggleClass(&#039;clbi-tnav-active&#039;, $menu.hasClass(&#039;worldbuilding-open&#039;));&lt;br /&gt;
        scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-tnav-worldbuilding&#039;, &#039;#clbi-sub-worldbuilding&#039;);&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-bnav-worldbuilding&#039;, &#039;#clbi-bottom-sub-worldbuilding&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-top-search-input, #clbi-bottom-search-input&#039;).on(&#039;keydown&#039;, function(e) {&lt;br /&gt;
    if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
        var q = $(this).val().trim();&lt;br /&gt;
        if (q) window.location.href = &#039;/index.php?search=&#039; + encodeURIComponent(q);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
        initClbiBottomShortcutSystem($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
watchClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// 페이지 전환 사운드&lt;br /&gt;
var transitionSound = new Audio(&#039;/index.php?title=특수:Redirect/file/Sfx-ui-001.mp3&#039;);&lt;br /&gt;
&lt;br /&gt;
(function() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
    transitionSound.volume = sfxOn ? master * sfx : 0;&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
function playStaticSound() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!sfxOn) return;&lt;br /&gt;
&lt;br /&gt;
    transitionSound.volume = master * sfx;&lt;br /&gt;
    transitionSound.currentTime = 0;&lt;br /&gt;
    transitionSound.play();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 현재 언어 감지&lt;br /&gt;
function getCurrentLang() {&lt;br /&gt;
    var langData = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    return langData ? (langData.getAttribute(&#039;data-lang&#039;) || &#039;ko&#039;) : &#039;ko&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizePageName(value) {&lt;br /&gt;
    return String(value || &#039;&#039;)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildWikiPath(title) {&lt;br /&gt;
    return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLangShortCode(lang) {&lt;br /&gt;
    var map = { ko: &#039;KR&#039;, en: &#039;EN&#039;, zh: &#039;ZH&#039;, ja: &#039;JA&#039;, ru: &#039;RU&#039;, es: &#039;ES&#039; };&lt;br /&gt;
    return map[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLanguageTargetTitle(lang) {&lt;br /&gt;
    var data = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    if (!data || !lang) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var keys = [&lt;br /&gt;
        &#039;data-&#039; + lang,&lt;br /&gt;
        &#039;data-page-&#039; + lang,&lt;br /&gt;
        &#039;data-title-&#039; + lang,&lt;br /&gt;
        &#039;data-target-&#039; + lang,&lt;br /&gt;
        &#039;data-lang-&#039; + lang&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var value = data.getAttribute(keys[i]);&lt;br /&gt;
        if (value) return value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeClbiHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var SIDEBAR_LANG_SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_TITLE = &#039;MediaWiki:LanguageStatus.json&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;English&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;Русский&#039;,&lt;br /&gt;
    es: &#039;Español&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_DIAL_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;ENG&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;РУС&#039;,&lt;br /&gt;
    es: &#039;ESP&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_VALUES = {&lt;br /&gt;
    available: true,&lt;br /&gt;
    wip: true,&lt;br /&gt;
    unavailable: true&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageStatusRegistry = {};&lt;br /&gt;
var sidebarLanguageStatusLoaded = false;&lt;br /&gt;
var sidebarLanguageStatusLoading = false;&lt;br /&gt;
var sidebarLanguageStatusCallbacks = [];&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageState = {&lt;br /&gt;
    order: [&#039;ko&#039;, &#039;en&#039;, &#039;zh&#039;, &#039;ja&#039;, &#039;ru&#039;, &#039;es&#039;],&lt;br /&gt;
    currentLang: &#039;ko&#039;,&lt;br /&gt;
    baseIndex: 0,&lt;br /&gt;
    selectedIndex: 0,&lt;br /&gt;
    rotation: 0,&lt;br /&gt;
    dragging: false,&lt;br /&gt;
    dragMoved: false,&lt;br /&gt;
    dragStartX: 0,&lt;br /&gt;
    dragStartY: 0,&lt;br /&gt;
    dragStartRotation: 0,&lt;br /&gt;
    dragAxis: null,&lt;br /&gt;
    pointerCaptured: false,&lt;br /&gt;
    lastX: 0,&lt;br /&gt;
    lastTime: 0,&lt;br /&gt;
    releaseVelocity: 0,&lt;br /&gt;
    suppressClickUntil: 0,&lt;br /&gt;
    raf: null,&lt;br /&gt;
    pendingRotation: 0,&lt;br /&gt;
    snapTimer: null,&lt;br /&gt;
    inertiaRaf: null,&lt;br /&gt;
    navigateTimer: null,&lt;br /&gt;
    bound: false,&lt;br /&gt;
    boundElement: null,&lt;br /&gt;
    rotor: null,&lt;br /&gt;
    cx: 101,&lt;br /&gt;
    cy: 119,&lt;br /&gt;
    outerR: 109,&lt;br /&gt;
    innerR: 28,&lt;br /&gt;
    sectorAngle: 30,&lt;br /&gt;
    halfSector: 15,&lt;br /&gt;
    repeats: 8,&lt;br /&gt;
    dragSensitivity: 0.58,&lt;br /&gt;
    maxSpinVelocity: 1.75,&lt;br /&gt;
    minSpinVelocity: 0.055,&lt;br /&gt;
    spinDecel: 0.00185&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function createSidebarLanguageSvgEl(tag) {&lt;br /&gt;
    return document.createElementNS(SIDEBAR_LANG_SVG_NS, tag);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageIndex(index) {&lt;br /&gt;
    var length = sidebarLanguageState.order.length;&lt;br /&gt;
    var normalized = index % length;&lt;br /&gt;
    return normalized &amp;lt; 0 ? normalized + length : normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageDialName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_DIAL_LABELS[lang] || getSidebarLanguageName(lang);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageStatusValue(value) {&lt;br /&gt;
    value = String(value == null ? &#039;&#039; : value).toLowerCase().trim();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_STATUS_VALUES[value] ? value : &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusPageKey() {&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
&lt;br /&gt;
    return normalized || raw || &#039;대문&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusEntry() {&lt;br /&gt;
    var registry = sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    var pages = registry.pages &amp;amp;&amp;amp; typeof registry.pages === &#039;object&#039; ? registry.pages : registry;&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
    var title = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var keys = [&lt;br /&gt;
        normalized,&lt;br /&gt;
        raw,&lt;br /&gt;
        raw.replace(/_/g, &#039; &#039;),&lt;br /&gt;
        normalized.replace(/ /g, &#039;_&#039;),&lt;br /&gt;
        title,&lt;br /&gt;
        title.replace(/_/g, &#039; &#039;)&lt;br /&gt;
    ];&lt;br /&gt;
    var i;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; keys.length; i += 1) {&lt;br /&gt;
        if (keys[i] &amp;amp;&amp;amp; pages[keys[i]] &amp;amp;&amp;amp; typeof pages[keys[i]] === &#039;object&#039;) {&lt;br /&gt;
            return pages[keys[i]];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {};&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusOverride(lang) {&lt;br /&gt;
    var entry = getSidebarLanguageStatusEntry();&lt;br /&gt;
    return normalizeSidebarLanguageStatusValue(entry[lang]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function flushSidebarLanguageStatusCallbacks() {&lt;br /&gt;
    var callbacks = sidebarLanguageStatusCallbacks.slice();&lt;br /&gt;
    sidebarLanguageStatusCallbacks.length = 0;&lt;br /&gt;
&lt;br /&gt;
    callbacks.forEach(function(callback) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
            callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadSidebarLanguageStatusRegistry(callback, force) {&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        sidebarLanguageStatusCallbacks.push(callback);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoaded &amp;amp;&amp;amp; !force) {&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoading) return;&lt;br /&gt;
&lt;br /&gt;
    sidebarLanguageStatusLoading = true;&lt;br /&gt;
&lt;br /&gt;
    function finishLanguageStatus(parsed) {&lt;br /&gt;
        sidebarLanguageStatusRegistry = parsed &amp;amp;&amp;amp; typeof parsed === &#039;object&#039; ? parsed : {};&lt;br /&gt;
        sidebarLanguageStatusLoaded = true;&lt;br /&gt;
        sidebarLanguageStatusLoading = false;&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!force &amp;amp;&amp;amp; window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.fetchJsonRef === &#039;function&#039;) {&lt;br /&gt;
        window.EntryStore.fetchJsonRef(SIDEBAR_LANGUAGE_STATUS_TITLE, { noStore: false })&lt;br /&gt;
            .then(function (parsed) { finishLanguageStatus(parsed); })&lt;br /&gt;
            .catch(function () { finishLanguageStatus({}); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    (function () {&lt;br /&gt;
        var url = mw.util.getUrl(SIDEBAR_LANGUAGE_STATUS_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, SIDEBAR_LANGUAGE_STATUS_TITLE);&lt;br /&gt;
        }&lt;br /&gt;
        $.ajax({&lt;br /&gt;
            url: url,&lt;br /&gt;
            dataType: &#039;text&#039;,&lt;br /&gt;
            cache: true&lt;br /&gt;
        }).done(function(text) {&lt;br /&gt;
            var parsed = {};&lt;br /&gt;
&lt;br /&gt;
            try {&lt;br /&gt;
                parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                console.error(&#039;LanguageStatus.json parse failed:&#039;, err);&lt;br /&gt;
                parsed = {};&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            finishLanguageStatus(parsed);&lt;br /&gt;
        }).fail(function() {&lt;br /&gt;
            finishLanguageStatus({});&lt;br /&gt;
        });&lt;br /&gt;
    })();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.CLBI_LANGUAGE_STATUS = {&lt;br /&gt;
    title: SIDEBAR_LANGUAGE_STATUS_TITLE,&lt;br /&gt;
    languages: sidebarLanguageState.order.slice(),&lt;br /&gt;
    labels: SIDEBAR_LANGUAGE_LABELS,&lt;br /&gt;
    dialLabels: SIDEBAR_LANGUAGE_DIAL_LABELS,&lt;br /&gt;
    getPageKey: getSidebarLanguageStatusPageKey,&lt;br /&gt;
    getRegistry: function() {&lt;br /&gt;
        return sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    },&lt;br /&gt;
    reload: function(callback) {&lt;br /&gt;
        sidebarLanguageStatusLoaded = false;&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            renderSidebarLanguageBox();&lt;br /&gt;
            if (typeof callback === &#039;function&#039;) callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }, true);&lt;br /&gt;
    },&lt;br /&gt;
    refreshDial: function() {&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageMeta(lang) {&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var targetTitle = getLanguageTargetTitle(lang);&lt;br /&gt;
    var isCurrent = lang === currentLang;&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        lang: lang,&lt;br /&gt;
        code: getLangShortCode(lang),&lt;br /&gt;
        name: getSidebarLanguageName(lang),&lt;br /&gt;
        dialName: getSidebarLanguageDialName(lang),&lt;br /&gt;
        targetTitle: targetTitle,&lt;br /&gt;
        isCurrent: isCurrent,&lt;br /&gt;
        canMove: !!targetTitle &amp;amp;&amp;amp; !isCurrent&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatus(meta) {&lt;br /&gt;
    var override;&lt;br /&gt;
&lt;br /&gt;
    if (!meta) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (meta.isCurrent) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-current&#039;,&lt;br /&gt;
            label: &#039;CURRENT&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    override = getSidebarLanguageStatusOverride(meta.lang);&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;wip&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;WIP&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;unavailable&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;available&#039; || meta.targetTitle) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: meta.targetTitle ? &#039;is-ready&#039; : &#039;is-locked&#039;,&lt;br /&gt;
            label: meta.targetTitle ? &#039;AVAILABLE&#039; : &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: !!meta.targetTitle&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        className: &#039;is-locked&#039;,&lt;br /&gt;
        label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
        canApply: false&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguageRad(deg) {&lt;br /&gt;
    return (deg * Math.PI) / 180;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguagePointAt(radius, deg) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var angle = sidebarLanguageRad(deg);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        x: state.cx + Math.sin(angle) * radius,&lt;br /&gt;
        y: state.cy - Math.cos(angle) * radius&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageSectorPath(start, end) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var p1 = sidebarLanguagePointAt(state.outerR, start);&lt;br /&gt;
    var p2 = sidebarLanguagePointAt(state.outerR, end);&lt;br /&gt;
    var p3 = sidebarLanguagePointAt(state.innerR, end);&lt;br /&gt;
    var p4 = sidebarLanguagePointAt(state.innerR, start);&lt;br /&gt;
    var largeArc = Math.abs(end - start) &amp;gt; 180 ? 1 : 0;&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;M&#039;, p1.x.toFixed(3), p1.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.outerR, state.outerR, 0, largeArc, 1, p2.x.toFixed(3), p2.y.toFixed(3),&lt;br /&gt;
        &#039;L&#039;, p3.x.toFixed(3), p3.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.innerR, state.innerR, 0, largeArc, 0, p4.x.toFixed(3), p4.y.toFixed(3),&lt;br /&gt;
        &#039;Z&#039;&lt;br /&gt;
    ].join(&#039; &#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageShellPath() {&lt;br /&gt;
    return getSidebarLanguageSectorPath(-68, 68);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageByStep(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        index: index,&lt;br /&gt;
        meta: getSidebarLanguageMeta(state.order[index])&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewIndex() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    return normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewMeta() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    return getSidebarLanguageMeta(state.order[getSidebarLanguagePreviewIndex()]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeSidebarLanguageSector(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var item = getSidebarLanguageByStep(step);&lt;br /&gt;
    var group = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    var path = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    var label = createSidebarLanguageSvgEl(&#039;text&#039;);&lt;br /&gt;
    var labelY = state.cy - 78;&lt;br /&gt;
    var angle = step * state.sectorAngle;&lt;br /&gt;
&lt;br /&gt;
    group.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-group&#039;);&lt;br /&gt;
    group.setAttribute(&#039;data-step&#039;, String(step));&lt;br /&gt;
    group.setAttribute(&#039;data-index&#039;, String(item.index));&lt;br /&gt;
    group.setAttribute(&#039;data-lang&#039;, item.meta.lang);&lt;br /&gt;
    group.setAttribute(&#039;transform&#039;, &#039;rotate(&#039; + angle + &#039; &#039; + state.cx + &#039; &#039; + state.cy + &#039;)&#039;);&lt;br /&gt;
&lt;br /&gt;
    path.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector&#039;);&lt;br /&gt;
    path.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
&lt;br /&gt;
    label.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-label&#039;);&lt;br /&gt;
    label.setAttribute(&#039;x&#039;, String(state.cx));&lt;br /&gt;
    label.setAttribute(&#039;y&#039;, String(labelY + 5));&lt;br /&gt;
    label.textContent = item.meta.dialName || item.meta.name;&lt;br /&gt;
&lt;br /&gt;
    group.appendChild(path);&lt;br /&gt;
    group.appendChild(label);&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        if (sidebarLanguageState.dragging || performance.now() &amp;lt; sidebarLanguageState.suppressClickUntil) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        snapSidebarLanguageToStep(parseInt(group.getAttribute(&#039;data-step&#039;) || &#039;0&#039;, 10), true);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return group;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageWheel() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var svg;&lt;br /&gt;
    var defs;&lt;br /&gt;
    var clip;&lt;br /&gt;
    var clipPath;&lt;br /&gt;
    var shadowBlur;&lt;br /&gt;
    var blur;&lt;br /&gt;
    var fixedDepthGradient;&lt;br /&gt;
    var shell;&lt;br /&gt;
    var clipped;&lt;br /&gt;
    var rotor;&lt;br /&gt;
    var fixedDepthPath;&lt;br /&gt;
    var fixedFocus;&lt;br /&gt;
    var shadowSoft;&lt;br /&gt;
    var shadowHard;&lt;br /&gt;
    var rim;&lt;br /&gt;
    var pointer;&lt;br /&gt;
    var tri;&lt;br /&gt;
    var line;&lt;br /&gt;
    var step;&lt;br /&gt;
&lt;br /&gt;
    if (!fan) return;&lt;br /&gt;
&lt;br /&gt;
    fan.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    svg = createSidebarLanguageSvgEl(&#039;svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fan-svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 202 150&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;xMidYMid meet&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;role&#039;, &#039;img&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;aria-label&#039;, &#039;언어 선택 다이얼&#039;);&lt;br /&gt;
&lt;br /&gt;
    defs = createSidebarLanguageSvgEl(&#039;defs&#039;);&lt;br /&gt;
&lt;br /&gt;
    clip = createSidebarLanguageSvgEl(&#039;clipPath&#039;);&lt;br /&gt;
    clip.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fan-clip&#039;);&lt;br /&gt;
    clipPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    clipPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    clip.appendChild(clipPath);&lt;br /&gt;
&lt;br /&gt;
    shadowBlur = createSidebarLanguageSvgEl(&#039;filter&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-shadow-blur&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;x&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;y&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;width&#039;, &#039;140%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;height&#039;, &#039;140%&#039;);&lt;br /&gt;
    blur = createSidebarLanguageSvgEl(&#039;feGaussianBlur&#039;);&lt;br /&gt;
    blur.setAttribute(&#039;stdDeviation&#039;, &#039;3&#039;);&lt;br /&gt;
    shadowBlur.appendChild(blur);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthGradient = createSidebarLanguageSvgEl(&#039;linearGradient&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x2&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y2&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    [&lt;br /&gt;
        [&#039;0%&#039;, &#039;#ffffff&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;34%&#039;, &#039;#ffffff&#039;, &#039;0.006&#039;],&lt;br /&gt;
        [&#039;58%&#039;, &#039;#000000&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;100%&#039;, &#039;#000000&#039;, &#039;0.250&#039;]&lt;br /&gt;
    ].forEach(function(item) {&lt;br /&gt;
        var stop = createSidebarLanguageSvgEl(&#039;stop&#039;);&lt;br /&gt;
        stop.setAttribute(&#039;offset&#039;, item[0]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-color&#039;, item[1]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-opacity&#039;, item[2]);&lt;br /&gt;
        fixedDepthGradient.appendChild(stop);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    defs.appendChild(clip);&lt;br /&gt;
    defs.appendChild(shadowBlur);&lt;br /&gt;
    defs.appendChild(fixedDepthGradient);&lt;br /&gt;
    svg.appendChild(defs);&lt;br /&gt;
&lt;br /&gt;
    shell = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;class&#039;, &#039;sidebar-lang-shell&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shell);&lt;br /&gt;
&lt;br /&gt;
    clipped = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    clipped.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    rotor = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;class&#039;, &#039;sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
&lt;br /&gt;
    for (step = -state.repeats; step &amp;lt;= state.repeats; step += 1) {&lt;br /&gt;
        rotor.appendChild(makeSidebarLanguageSector(step));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    clipped.appendChild(rotor);&lt;br /&gt;
    svg.appendChild(clipped);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(fixedDepthPath);&lt;br /&gt;
&lt;br /&gt;
    fixedFocus = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-focus&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
    svg.appendChild(fixedFocus);&lt;br /&gt;
&lt;br /&gt;
    shadowSoft = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-soft&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowSoft);&lt;br /&gt;
&lt;br /&gt;
    shadowHard = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-hard&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowHard);&lt;br /&gt;
&lt;br /&gt;
    rim = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;class&#039;, &#039;sidebar-lang-rim&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(rim);&lt;br /&gt;
&lt;br /&gt;
    pointer = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-pointer&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    tri = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-triangle&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;d&#039;, &#039;M &#039; + (state.cx - 10) + &#039; 10 L &#039; + (state.cx + 10) + &#039; 10 L &#039; + state.cx + &#039; 26 Z&#039;);&lt;br /&gt;
    pointer.appendChild(tri);&lt;br /&gt;
&lt;br /&gt;
    line = createSidebarLanguageSvgEl(&#039;line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;x1&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;x2&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;y1&#039;, &#039;24&#039;);&lt;br /&gt;
    line.setAttribute(&#039;y2&#039;, &#039;112&#039;);&lt;br /&gt;
    pointer.appendChild(line);&lt;br /&gt;
&lt;br /&gt;
    svg.appendChild(pointer);&lt;br /&gt;
    fan.appendChild(svg);&lt;br /&gt;
&lt;br /&gt;
    state.rotor = rotor;&lt;br /&gt;
    setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateSidebarLanguageDial() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var meta = getSidebarLanguagePreviewMeta();&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
    var selectedValue = document.getElementById(&#039;clbi-sidebar-lang-selected-value&#039;);&lt;br /&gt;
    var availabilityPanel = document.getElementById(&#039;clbi-sidebar-lang-availability-panel&#039;);&lt;br /&gt;
    var availabilityValue = document.getElementById(&#039;clbi-sidebar-lang-availability-value&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (selectedValue) {&lt;br /&gt;
        selectedValue.textContent = meta.name;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityPanel) {&lt;br /&gt;
        availabilityPanel.classList.remove(&#039;is-ready&#039;, &#039;is-current&#039;, &#039;is-locked&#039;);&lt;br /&gt;
        availabilityPanel.classList.add(status.className);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityValue) {&lt;br /&gt;
        availabilityValue.textContent = status.label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (apply) {&lt;br /&gt;
        apply.classList.toggle(&#039;is-disabled&#039;, !status.canApply);&lt;br /&gt;
        apply.setAttribute(&#039;aria-disabled&#039;, status.canApply ? &#039;false&#039; : &#039;true&#039;);&lt;br /&gt;
        apply.setAttribute(&#039;aria-label&#039;, status.canApply ? (meta.name + &#039; 적용&#039;) : (meta.isCurrent ? &#039;현재 언어&#039; : &#039;사용할 수 없는 언어&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (selector) {&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-lang&#039;, meta.lang);&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-code&#039;, meta.code);&lt;br /&gt;
        selector.classList.toggle(&#039;is-current&#039;, meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-ready&#039;, status.canApply);&lt;br /&gt;
        selector.classList.toggle(&#039;is-locked&#039;, !status.canApply &amp;amp;&amp;amp; !meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-dragging&#039;, !!state.dragging);&lt;br /&gt;
        selector.classList.toggle(&#039;is-spinning&#039;, !!state.inertiaRaf);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        meta: meta,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageRotation(value, animate) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.rotation = value;&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (!state.rotor) return;&lt;br /&gt;
&lt;br /&gt;
    if (animate) {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-snapping&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.rotor.style.transform = &#039;rotate(&#039; + state.rotation.toFixed(3) + &#039;deg)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestSidebarLanguageRotation(value) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.pendingRotation = value;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) return;&lt;br /&gt;
&lt;br /&gt;
    state.raf = requestAnimationFrame(function() {&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
        setSidebarLanguageRotation(state.pendingRotation, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function cancelSidebarLanguageSpin() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    if (state.inertiaRaf) {&lt;br /&gt;
        cancelAnimationFrame(state.inertiaRaf);&lt;br /&gt;
        state.inertiaRaf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finishSidebarLanguageSnap(nearestIndex, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    state.baseIndex = normalizeSidebarLanguageIndex(nearestIndex);&lt;br /&gt;
    state.selectedIndex = state.baseIndex;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping is-dragging is-spinning&#039;);&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        callback(getSidebarLanguageMeta(state.order[state.selectedIndex]));&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageToStep(step, animate, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var targetRotation = -step * state.sectorAngle;&lt;br /&gt;
    var nearestIndex = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.selectedIndex = nearestIndex;&lt;br /&gt;
    setSidebarLanguageRotation(targetRotation, !!animate);&lt;br /&gt;
&lt;br /&gt;
    state.snapTimer = setTimeout(function() {&lt;br /&gt;
        finishSidebarLanguageSnap(nearestIndex, callback);&lt;br /&gt;
    }, animate ? 230 : 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageNearest(callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    snapSidebarLanguageToStep(step, true, callback);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function startSidebarLanguageInertiaSpin(initialVelocity) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var velocity;&lt;br /&gt;
    var lastFrame;&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
&lt;br /&gt;
    velocity = Math.max(-state.maxSpinVelocity, Math.min(state.maxSpinVelocity, initialVelocity));&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(velocity) &amp;lt; state.minSpinVelocity) {&lt;br /&gt;
        snapSidebarLanguageNearest();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-spinning&#039;);&lt;br /&gt;
    lastFrame = performance.now();&lt;br /&gt;
&lt;br /&gt;
    function frame(now) {&lt;br /&gt;
        var dt = Math.min(34, Math.max(1, now - lastFrame));&lt;br /&gt;
        var sign = velocity &amp;lt; 0 ? -1 : 1;&lt;br /&gt;
        var nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        state.rotation += velocity * dt;&lt;br /&gt;
        setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
&lt;br /&gt;
        nextSpeed = Math.max(0, Math.abs(velocity) - (state.spinDecel * dt));&lt;br /&gt;
        velocity = sign * nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        if (nextSpeed &amp;lt;= state.minSpinVelocity) {&lt;br /&gt;
            state.inertiaRaf = null;&lt;br /&gt;
            $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleSidebarLanguageNavigation(meta) {&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
&lt;br /&gt;
    if (!meta || !status.canApply) return;&lt;br /&gt;
&lt;br /&gt;
    clearTimeout(sidebarLanguageState.navigateTimer);&lt;br /&gt;
    sidebarLanguageState.navigateTimer = setTimeout(function() {&lt;br /&gt;
        var title = getLanguageTargetTitle(meta.lang);&lt;br /&gt;
&lt;br /&gt;
        if (!title || meta.lang === getCurrentLang()) return;&lt;br /&gt;
&lt;br /&gt;
        window.location.href = buildWikiPath(title);&lt;br /&gt;
    }, 70);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageSelection(lang) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = state.order.indexOf(lang);&lt;br /&gt;
&lt;br /&gt;
    if (index &amp;lt; 0) index = state.order.indexOf(getCurrentLang());&lt;br /&gt;
    if (index &amp;lt; 0) index = 0;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) {&lt;br /&gt;
        cancelAnimationFrame(state.raf);&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.currentLang = lang;&lt;br /&gt;
    state.baseIndex = index;&lt;br /&gt;
    state.selectedIndex = index;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
    state.dragMoved = false;&lt;br /&gt;
    state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveSidebarLanguageSelection(delta) {&lt;br /&gt;
    snapSidebarLanguageToStep(-delta, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindSidebarLanguageSelector() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!selector || !fan || !apply) return;&lt;br /&gt;
&lt;br /&gt;
    if (state.bound &amp;amp;&amp;amp; state.boundElement === selector) return;&lt;br /&gt;
&lt;br /&gt;
    state.bound = true;&lt;br /&gt;
    state.boundElement = selector;&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerdown&#039;, function(e) {&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
        state.dragging = true;&lt;br /&gt;
        state.dragMoved = false;&lt;br /&gt;
        state.dragStartX = e.clientX;&lt;br /&gt;
        state.dragStartY = e.clientY || 0;&lt;br /&gt;
        state.dragStartRotation = state.rotation;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = performance.now();&lt;br /&gt;
        state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
        selector.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
        selector.classList.remove(&#039;is-snapping&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        Vertical page scrolling must stay available when the pointer starts on&lt;br /&gt;
        the language dial. Capture and preventDefault are delayed until a&lt;br /&gt;
        horizontal drag is confirmed.&lt;br /&gt;
        */&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointermove&#039;, function(e) {&lt;br /&gt;
        var now;&lt;br /&gt;
        var totalDx;&lt;br /&gt;
        var totalDy;&lt;br /&gt;
        var frameDx;&lt;br /&gt;
        var dt;&lt;br /&gt;
        var instantVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        totalDx = e.clientX - state.dragStartX;&lt;br /&gt;
        totalDy = (e.clientY || 0) - state.dragStartY;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragAxis &amp;amp;&amp;amp; (Math.abs(totalDx) &amp;gt; 4 || Math.abs(totalDy) &amp;gt; 4)) {&lt;br /&gt;
            state.dragAxis = Math.abs(totalDx) &amp;gt;= Math.abs(totalDy) ? &#039;x&#039; : &#039;y&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (state.dragAxis === &#039;y&#039;) {&lt;br /&gt;
                state.dragging = false;&lt;br /&gt;
                state.dragMoved = false;&lt;br /&gt;
                state.dragAxis = null;&lt;br /&gt;
                state.pointerCaptured = false;&lt;br /&gt;
                selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (fan.setPointerCapture &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
                try {&lt;br /&gt;
                    fan.setPointerCapture(e.pointerId);&lt;br /&gt;
                    state.pointerCaptured = true;&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    state.pointerCaptured = false;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragAxis !== &#039;x&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        now = performance.now();&lt;br /&gt;
        frameDx = e.clientX - state.lastX;&lt;br /&gt;
        dt = Math.max(1, now - state.lastTime);&lt;br /&gt;
&lt;br /&gt;
        if (Math.abs(totalDx) &amp;gt; 3) state.dragMoved = true;&lt;br /&gt;
&lt;br /&gt;
        instantVelocity = (frameDx * state.dragSensitivity) / dt;&lt;br /&gt;
        state.releaseVelocity = (state.releaseVelocity * 0.62) + (instantVelocity * 0.38);&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = now;&lt;br /&gt;
&lt;br /&gt;
        requestSidebarLanguageRotation(state.dragStartRotation + totalDx * state.dragSensitivity);&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function finishDrag(e) {&lt;br /&gt;
        var velocityAge;&lt;br /&gt;
        var throwVelocity;&lt;br /&gt;
        var wasHorizontal;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        wasHorizontal = state.dragAxis === &#039;x&#039;;&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (fan.releasePointerCapture &amp;amp;&amp;amp; state.pointerCaptured &amp;amp;&amp;amp; e &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
            try { fan.releasePointerCapture(e.pointerId); } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
&lt;br /&gt;
        if (!wasHorizontal &amp;amp;&amp;amp; !state.dragMoved) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        velocityAge = performance.now() - state.lastTime;&lt;br /&gt;
        throwVelocity = velocityAge &amp;gt; 120 ? 0 : state.releaseVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(throwVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            startSidebarLanguageInertiaSpin(throwVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerup&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;pointercancel&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;lostpointercapture&#039;, function() {&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(state.releaseVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
            startSidebarLanguageInertiaSpin(state.releaseVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    apply.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        snapSidebarLanguageNearest(function(meta) {&lt;br /&gt;
            scheduleSidebarLanguageNavigation(meta);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    selector.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
        if (e.key === &#039;ArrowLeft&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(-1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;ArrowRight&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;Enter&#039; || e.key === &#039; &#039;) {&lt;br /&gt;
            apply.click();&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageBox() {&lt;br /&gt;
    bindSidebarLanguageSelector();&lt;br /&gt;
    setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
&lt;br /&gt;
    if (!sidebarLanguageStatusLoaded) {&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadRecentChangesList(targetSelector, limit) {&lt;br /&gt;
    var $target = $(targetSelector);&lt;br /&gt;
&lt;br /&gt;
    if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
    var isNewsList = $target.closest(&#039;.clbi-left-news-box&#039;).length &amp;gt; 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.html((t &amp;amp;&amp;amp; t.loading) ? t.loading : &#039;불러오는 중...&#039;);&lt;br /&gt;
&lt;br /&gt;
    $.getJSON(&lt;br /&gt;
        &#039;/api.php?action=query&amp;amp;list=recentchanges&amp;amp;rclimit=&#039; + encodeURIComponent(limit || 5) + &#039;&amp;amp;rcprop=title|timestamp|user&amp;amp;format=json&amp;amp;rcnamespace=0&amp;amp;rctype=edit|new&#039;,&lt;br /&gt;
        function(data) {&lt;br /&gt;
            var items = data &amp;amp;&amp;amp; data.query ? data.query.recentchanges : [];&lt;br /&gt;
            var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (!items || !items.length) {&lt;br /&gt;
                $target.html(&#039;표시할 변경 사항이 없습니다.&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $.each(items, function(i, item) {&lt;br /&gt;
                var label = timeAgo(item.timestamp);&lt;br /&gt;
                var title = item.title || &#039;&#039;;&lt;br /&gt;
                var userName = item.user || &#039;Unknown&#039;;&lt;br /&gt;
                var pageHref = buildWikiPath(title);&lt;br /&gt;
                var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(userName) + &#039;.png&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (isNewsList) {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;news-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;news-recent-avatar&amp;quot; src=&amp;quot;&#039; + escapeHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;news-recent-main&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-meta&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-user&amp;quot;&amp;gt;@&#039; + escapeHtml(userName) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
                } else {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;clbi-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;clbi-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList) {&lt;br /&gt;
                $target.html(&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-recent-viewport&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-recent-stack&amp;quot;&amp;gt;&#039; + html + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
                    ensureNewsBottomFinish();&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                $target.html(html);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList &amp;amp;&amp;amp; typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
                scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $target.find(isNewsList ? &#039;.news-recent-item&#039; : &#039;.clbi-recent-item&#039;).each(function() {&lt;br /&gt;
                var wrap = $(this).find(isNewsList ? &#039;.news-recent-title-wrap&#039; : &#039;.clbi-recent-title-wrap&#039;);&lt;br /&gt;
                var title = $(this).find(isNewsList ? &#039;.news-recent-title&#039; : &#039;.clbi-recent-title&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (!wrap.length || !title.length) return;&lt;br /&gt;
&lt;br /&gt;
                var wrapW = wrap.width();&lt;br /&gt;
                var titleW = title[0].scrollWidth;&lt;br /&gt;
&lt;br /&gt;
                if (titleW &amp;gt; wrapW + 20) {&lt;br /&gt;
                    var duration = titleW / 40;&lt;br /&gt;
&lt;br /&gt;
                    title.css({&lt;br /&gt;
                        animation: &#039;clbi-scroll &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
                        &#039;--scroll-dist&#039;: &#039;-&#039; + (titleW - wrapW + 8) + &#039;px&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    ).fail(function() {&lt;br /&gt;
        var lang = getCurrentLang();&lt;br /&gt;
        var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
&lt;br /&gt;
        $target.html((t &amp;amp;&amp;amp; t.loadFail) ? t.loadFail : &#039;불러오기 실패&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureRecentViewport() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var viewport;&lt;br /&gt;
    var stack;&lt;br /&gt;
    var children;&lt;br /&gt;
&lt;br /&gt;
    if (!list) return null;&lt;br /&gt;
&lt;br /&gt;
    viewport = list.querySelector(&#039;:scope &amp;gt; .news-recent-viewport&#039;);&lt;br /&gt;
    stack = viewport ? viewport.querySelector(&#039;:scope &amp;gt; .news-recent-stack&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (viewport &amp;amp;&amp;amp; stack) return viewport;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(list.children || []);&lt;br /&gt;
&lt;br /&gt;
    viewport = document.createElement(&#039;div&#039;);&lt;br /&gt;
    viewport.className = &#039;news-recent-viewport&#039;;&lt;br /&gt;
&lt;br /&gt;
    stack = document.createElement(&#039;div&#039;);&lt;br /&gt;
    stack.className = &#039;news-recent-stack&#039;;&lt;br /&gt;
&lt;br /&gt;
    children.forEach(function (child) {&lt;br /&gt;
        if (child.classList &amp;amp;&amp;amp; child.classList.contains(&#039;news-recent-viewport&#039;)) return;&lt;br /&gt;
        stack.appendChild(child);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    viewport.appendChild(stack);&lt;br /&gt;
    list.appendChild(viewport);&lt;br /&gt;
&lt;br /&gt;
    return viewport;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ensureNewsBottomFinish() {&lt;br /&gt;
    var newsBox = document.querySelector(&#039;#clbi-left-sidebar .clbi-left-news-box&#039;);&lt;br /&gt;
    var content = newsBox ? newsBox.querySelector(&#039;.clbi-news-box&#039;) : null;&lt;br /&gt;
    var finish;&lt;br /&gt;
&lt;br /&gt;
    if (!content) return null;&lt;br /&gt;
&lt;br /&gt;
    finish = content.querySelector(&#039;:scope &amp;gt; .news-bottom-finish&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!finish) {&lt;br /&gt;
        finish = document.createElement(&#039;div&#039;);&lt;br /&gt;
        finish.className = &#039;news-bottom-finish&#039;;&lt;br /&gt;
        finish.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        content.appendChild(finish);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return finish;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateAdaptiveLeftRecentItems() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: 좌측 사이드는 뉴스 확장형 flex 레이아웃이 높이를 담당한다.&lt;br /&gt;
    이전 adaptive 코드는 항목을 숨기거나 mask/fade를 걸기 위한 것이었으므로&lt;br /&gt;
    여기서는 잔여 상태만 정리하고 DOM 래퍼만 보장한다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftRecentAdaptiveState();&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureRecentViewport === &#039;function&#039;) {&lt;br /&gt;
        ensureRecentViewport();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
        ensureNewsBottomFinish();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleAdaptiveLeftRecentItems() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateAdaptiveLeftRecentItems();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 80);&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateClbiContentBottomGap(iteration) {&lt;br /&gt;
    var content = document.querySelector(&#039;.container-fluid.liberty-content&#039;);&lt;br /&gt;
    var main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var desiredGap = 8;&lt;br /&gt;
    var rootStyle;&lt;br /&gt;
    var scale = 1;&lt;br /&gt;
    var contentRect;&lt;br /&gt;
    var bottomTop;&lt;br /&gt;
    var targetHeight;&lt;br /&gt;
    var currentHeight;&lt;br /&gt;
    var visualGap;&lt;br /&gt;
&lt;br /&gt;
    iteration = iteration || 0;&lt;br /&gt;
&lt;br /&gt;
    if (!content || !main || !bottomNav) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    하단 간격은 scale 모드에서도 같은 기준으로 계산한다.&lt;br /&gt;
    transform:scale()이 걸리면 getBoundingClientRect()는 축소된 화면 좌표를 반환하므로,&lt;br /&gt;
    목표 간격 8px도 scale을 곱한 화면 좌표로 비교하고 다시 design px로 환산한다.&lt;br /&gt;
&lt;br /&gt;
    목표:&lt;br /&gt;
    .liberty-content-main.bottom === #clbi-bottom-nav-wrap.top - 8px&lt;br /&gt;
    */&lt;br /&gt;
    if (document.body &amp;amp;&amp;amp; document.body.classList &amp;amp;&amp;amp; document.body.classList.contains(&#039;clbi-shell-vertical-scale&#039;)) {&lt;br /&gt;
        rootStyle = window.getComputedStyle(document.documentElement);&lt;br /&gt;
        scale = parseFloat(rootStyle.getPropertyValue(&#039;--clbi-shell-scale&#039;)) || 1;&lt;br /&gt;
        scale = Math.max(0.25, scale);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    contentRect = content.getBoundingClientRect();&lt;br /&gt;
    bottomTop = bottomNav.getBoundingClientRect().top;&lt;br /&gt;
    visualGap = desiredGap * scale;&lt;br /&gt;
    targetHeight = Math.floor((bottomTop - contentRect.top - visualGap) / scale);&lt;br /&gt;
    targetHeight = Math.max(120, targetHeight);&lt;br /&gt;
&lt;br /&gt;
    currentHeight = Math.round(content.getBoundingClientRect().height / scale);&lt;br /&gt;
&lt;br /&gt;
    content.style.setProperty(&#039;--clbi-content-extra&#039;, &#039;0px&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;max-height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(currentHeight - targetHeight) &amp;gt;= 1 &amp;amp;&amp;amp; iteration &amp;lt; 4) {&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            updateClbiContentBottomGap(iteration + 1);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiContentBottomGap() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 40);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 120);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 280);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 520);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateLeftBillboardAdaptive() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: Ad는 이미지/CRT 비율을 유지하는 고정 슬롯이다.&lt;br /&gt;
    남는 세로 공간은 뉴스 박스가 흡수하므로, Ad에 하단 finish를 늘리거나&lt;br /&gt;
    title-only 상태로 접는 adaptive 보정은 사용하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftBillboardAdaptiveState();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftBillboardAdaptive() {&lt;br /&gt;
    window.requestAnimationFrame(updateLeftBillboardAdaptive);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 80);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftSidebarVerticalFit() {&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
            scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
            scheduleLeftBillboardAdaptive();&lt;br /&gt;
        }&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시대 문서 전용 왼쪽 사이드바 이미지&lt;br /&gt;
function updateLeftSidebarNationsImage() {&lt;br /&gt;
    $(&#039;#clbi-left-nations-image&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setProfileActionLabel(selector, text) {&lt;br /&gt;
    var target = $(selector);&lt;br /&gt;
    var label = target.find(&#039;.profile-action-label&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (label.length) {&lt;br /&gt;
        label.text(text);&lt;br /&gt;
    } else {&lt;br /&gt;
        target.text(text);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 사이드바 업데이트&lt;br /&gt;
function updateSidebar() {&lt;br /&gt;
    if (!window.LANG) {&lt;br /&gt;
        setTimeout(updateSidebar, 100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
&lt;br /&gt;
    var newsTitle = t.news || &#039;뉴스&#039;;&lt;br /&gt;
    var changelogTitle = t.changelog || &#039;체인지로그&#039;;&lt;br /&gt;
    var recentTitle = t.recentChanges || &#039;최근 변경&#039;;&lt;br /&gt;
    var languageTitle = t.language || &#039;언어&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-language&#039;).text(languageTitle);&lt;br /&gt;
    renderSidebarLanguageBox();&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-news&#039;).text(newsTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-recent-main&#039;).text(recentTitle);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-search a&#039;).text(t.search);&lt;br /&gt;
    $(&#039;#clbi-search-input&#039;).attr(&#039;placeholder&#039;, t.search + &#039;...&#039;);&lt;br /&gt;
    $(&#039;#clbi-title-recent a&#039;).text(recentTitle);&lt;br /&gt;
    $(&#039;#clbi-title-guide-label&#039;).text(t.guide);&lt;br /&gt;
    $(&#039;#clbi-guide-link&#039;).text(t.getStarted);&lt;br /&gt;
    $(&#039;#clbi-title-links-label&#039;).text(t.links);&lt;br /&gt;
&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-contribution&#039;, t.contribution);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-watchlist&#039;, t.watchlist);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-preferences&#039;, t.preferences);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-logout&#039;, t.logout);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-login&#039;, t.login);&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var specialPage = String(mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
$(&#039;#clbi-left-news-recent-title&#039;).text(&#039;RECENT CHANGES&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.clbi-user-btn&#039;).removeClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;Contributions&#039; ||&lt;br /&gt;
        specialPage === &#039;기여&#039; ||&lt;br /&gt;
        pageName.indexOf(&#039;특수:기여&#039;) === 0 ||&lt;br /&gt;
        pageName.indexOf(&#039;Special:Contributions&#039;) === 0&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-contribution&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (specialPage === &#039;Watchlist&#039;) {&lt;br /&gt;
        $(&#039;#clbi-btn-watchlist&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;설정&#039; ||&lt;br /&gt;
        pageName === &#039;특수:설정&#039; ||&lt;br /&gt;
        pageName === &#039;Special:설정&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-preferences&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.toggleBtn&#039;).each(function() {&lt;br /&gt;
        var btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        if (!$(&#039;#&#039; + btn.data(&#039;target&#039;)).hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            btn.text(t.expand);&lt;br /&gt;
        } else {&lt;br /&gt;
            btn.text(t.collapse);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    updateLeftSidebarNationsImage();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function canShowContentTools() {&lt;br /&gt;
    // 비로그인 사용자는 편집/역사/공유 버튼을 숨김&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // MediaWiki가 현재 문서를 편집 가능하지 않다고 판단하면 숨김&lt;br /&gt;
    var isEditable = mw.config.get(&#039;wgIsProbablyEditable&#039;);&lt;br /&gt;
    if (isEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var relevantEditable = mw.config.get(&#039;wgRelevantPageIsProbablyEditable&#039;);&lt;br /&gt;
    if (relevantEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkNodes(root) {&lt;br /&gt;
    var seen = [];&lt;br /&gt;
    var nodes = [];&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
&lt;br /&gt;
    $root.find(&#039;#catlinks, .catlinks&#039;).add($root.filter(&#039;#catlinks, .catlinks&#039;)).each(function () {&lt;br /&gt;
        if (seen.indexOf(this) !== -1) return;&lt;br /&gt;
        seen.push(this);&lt;br /&gt;
        nodes.push(this);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return nodes;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinksTarget(root) {&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
    var parserOutput = $root.find(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
    var main = $root.find(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.liberty-content-main&#039;)) {&lt;br /&gt;
        parserOutput = $(root).find(&#039;.mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.mw-parser-output&#039;)) {&lt;br /&gt;
        parserOutput = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (parserOutput.length) return parserOutput;&lt;br /&gt;
    if (main.length) return main;&lt;br /&gt;
&lt;br /&gt;
    if (!root) {&lt;br /&gt;
        parserOutput = $(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
        if (parserOutput.length) return parserOutput;&lt;br /&gt;
        if (main.length) return main;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return $();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_CATLINKS_FETCH_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function getCurrentPageTitleForCatlinks() {&lt;br /&gt;
    return String(&lt;br /&gt;
        mw.config.get(&#039;wgPageName&#039;) ||&lt;br /&gt;
        mw.config.get(&#039;wgRelevantPageName&#039;) ||&lt;br /&gt;
        &#039;&#039;&lt;br /&gt;
    ).trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function shouldFetchCatlinks() {&lt;br /&gt;
    var pageName = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) return false;&lt;br /&gt;
    if (specialPage) return false;&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearCatlinksInlineHiding(cat) {&lt;br /&gt;
    if (!cat || !cat.style) return;&lt;br /&gt;
&lt;br /&gt;
    cat.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-normal-catlinks, #mw-normal-catlinks, .mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        if (!this.style) return;&lt;br /&gt;
        this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function exposeHiddenCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        this.classList.remove(&#039;mw-hidden-cats-hidden&#039;);&lt;br /&gt;
        this.classList.add(&#039;mw-hidden-cats-user-shown&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (this.style) {&lt;br /&gt;
            this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkTextContent(cat) {&lt;br /&gt;
    var clone;&lt;br /&gt;
    var text;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    clone = cat.cloneNode(true);&lt;br /&gt;
    $(clone).find(&#039;script, style&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    text = String(clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*:\s*/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*$/i, &#039;&#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function hasRenderedCatlinkItems(cat) {&lt;br /&gt;
    var hasLink;&lt;br /&gt;
    var hasListText;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    hasLink = false;&lt;br /&gt;
    $(cat).find(&#039;a&#039;).each(function () {&lt;br /&gt;
        var text = String($(this).text() || &#039;&#039;).trim();&lt;br /&gt;
        var href = String(this.getAttribute(&#039;href&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        if (text || href) hasLink = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasLink) return true;&lt;br /&gt;
&lt;br /&gt;
    hasListText = false;&lt;br /&gt;
    $(cat).find(&#039;li&#039;).each(function () {&lt;br /&gt;
        if (String($(this).text() || &#039;&#039;).trim()) hasListText = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasListText) return true;&lt;br /&gt;
&lt;br /&gt;
    return !!getCatlinkTextContent(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCategoryTitle(rawTitle) {&lt;br /&gt;
    var title = String(rawTitle == null ? &#039;&#039; : rawTitle).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!title) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    title = title.replace(/_/g, &#039; &#039;);&lt;br /&gt;
&lt;br /&gt;
    if (/^(Category|분류):/i.test(title)) {&lt;br /&gt;
        return title;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;분류:&#039; + title;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeCategoryLinkTitle(rawTitle) {&lt;br /&gt;
    return String(rawTitle || &#039;&#039;)&lt;br /&gt;
        .replace(/^Category:/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^분류:/, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function dedupeCatlinkCategories(categories) {&lt;br /&gt;
    var seen = {};&lt;br /&gt;
    var result = [];&lt;br /&gt;
&lt;br /&gt;
    (categories || []).forEach(function (item) {&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var hidden = false;&lt;br /&gt;
&lt;br /&gt;
        if (typeof item === &#039;string&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item);&lt;br /&gt;
        } else if (item &amp;amp;&amp;amp; typeof item === &#039;object&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item.title || item.name || item.category || &#039;&#039;);&lt;br /&gt;
            hidden = item.hidden !== undefined || item.isHidden === true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
        if (seen[title]) return;&lt;br /&gt;
&lt;br /&gt;
        seen[title] = true;&lt;br /&gt;
        result.push({ title: title, hidden: hidden });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getConfigCatlinksCategories() {&lt;br /&gt;
    var normal = mw.config.get(&#039;wgCategories&#039;) || [];&lt;br /&gt;
    var hidden = mw.config.get(&#039;wgHiddenCategories&#039;) || [];&lt;br /&gt;
    var categories = [];&lt;br /&gt;
&lt;br /&gt;
    if (!Array.isArray(normal)) normal = [];&lt;br /&gt;
    if (!Array.isArray(hidden)) hidden = [];&lt;br /&gt;
&lt;br /&gt;
    normal.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: false });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    hidden.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: true });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return dedupeCatlinkCategories(categories);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksReady(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksEmpty(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksPending(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-fetching&#039;, &#039;1&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderFetchedCatlinks(cat, categories, pageTitle) {&lt;br /&gt;
    var container;&lt;br /&gt;
    var ul;&lt;br /&gt;
    var normalized;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    normalized = dedupeCatlinkCategories(categories);&lt;br /&gt;
&lt;br /&gt;
    if (!normalized.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cat.innerHTML = &#039;&#039;;&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-api-populated&#039;);&lt;br /&gt;
&lt;br /&gt;
    container = document.createElement(&#039;div&#039;);&lt;br /&gt;
    container.className = &#039;mw-normal-catlinks&#039;;&lt;br /&gt;
    container.appendChild(document.createTextNode(&#039;분류: &#039;));&lt;br /&gt;
&lt;br /&gt;
    ul = document.createElement(&#039;ul&#039;);&lt;br /&gt;
&lt;br /&gt;
    normalized.forEach(function (item) {&lt;br /&gt;
        var title = String(item &amp;amp;&amp;amp; item.title ? item.title : &#039;&#039;).trim();&lt;br /&gt;
        var li;&lt;br /&gt;
        var a;&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
&lt;br /&gt;
        li = document.createElement(&#039;li&#039;);&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = mw.util.getUrl(title);&lt;br /&gt;
        a.title = title;&lt;br /&gt;
        a.textContent = makeCategoryLinkTitle(title);&lt;br /&gt;
&lt;br /&gt;
        if (item.hidden) {&lt;br /&gt;
            li.className = &#039;clbi-hidden-category-item&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        li.appendChild(a);&lt;br /&gt;
        ul.appendChild(li);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (!ul.children.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.appendChild(ul);&lt;br /&gt;
    cat.appendChild(container);&lt;br /&gt;
    markCatlinksReady(cat, pageTitle);&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksForPage(pageTitle, callback) {&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (typeof pageTitle === &#039;function&#039;) {&lt;br /&gt;
        callback = pageTitle;&lt;br /&gt;
        pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    pageTitle = String(pageTitle || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!pageTitle || !shouldFetchCatlinks()) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;categories&#039;,&lt;br /&gt;
        titles: pageTitle,&lt;br /&gt;
        cllimit: &#039;max&#039;,&lt;br /&gt;
        clprop: &#039;hidden&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).done(function (data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages &amp;amp;&amp;amp; pages.length ? pages[0] : null;&lt;br /&gt;
        var categories = page &amp;amp;&amp;amp; page.categories ? page.categories : [];&lt;br /&gt;
&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback(categories || [], pageTitle);&lt;br /&gt;
    }).fail(function () {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finalizeEmptyCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    markCatlinksEmpty(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksIfNeeded(cat) {&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(cat, configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) {&lt;br /&gt;
        finalizeEmptyCatlinks(cat);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (cat.getAttribute(&#039;data-clbi-catlinks-fetching&#039;) === &#039;1&#039; &amp;amp;&amp;amp; cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) === pageTitle) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
    markCatlinksPending(cat, pageTitle);&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (!cat || !cat.isConnected) return;&lt;br /&gt;
        if (cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) !== requestedPage) return;&lt;br /&gt;
&lt;br /&gt;
        if (!renderFetchedCatlinks(cat, categories, requestedPage)) {&lt;br /&gt;
            finalizeEmptyCatlinks(cat);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCatlinksPanel(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksIfNeeded(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createCatlinksPanel(target, className) {&lt;br /&gt;
    var cat;&lt;br /&gt;
&lt;br /&gt;
    if (!target || !target.length) return null;&lt;br /&gt;
&lt;br /&gt;
    cat = document.createElement(&#039;div&#039;);&lt;br /&gt;
    cat.id = &#039;catlinks&#039;;&lt;br /&gt;
    cat.className = className || &#039;catlinks clbi-catlinks-created clbi-catlinks-pending&#039;;&lt;br /&gt;
    target.append(cat);&lt;br /&gt;
    return cat;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prepareSpaCatlinksBeforeInsert(root) {&lt;br /&gt;
    var nodes;&lt;br /&gt;
    var target;&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    nodes = getCatlinkNodes(root);&lt;br /&gt;
&lt;br /&gt;
    nodes.forEach(function (node) {&lt;br /&gt;
        node.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
        clearCatlinksInlineHiding(node);&lt;br /&gt;
        exposeHiddenCatlinks(node);&lt;br /&gt;
&lt;br /&gt;
        if (hasRenderedCatlinkItems(node)) {&lt;br /&gt;
            markCatlinksReady(node, pageTitle);&lt;br /&gt;
        } else if (configCategories.length) {&lt;br /&gt;
            renderFetchedCatlinks(node, configCategories, pageTitle);&lt;br /&gt;
        } else {&lt;br /&gt;
            markCatlinksEmpty(node);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (nodes.length || !configCategories.length) return;&lt;br /&gt;
&lt;br /&gt;
    target = getCatlinksTarget(root);&lt;br /&gt;
    if (!target.length) target = $(root);&lt;br /&gt;
&lt;br /&gt;
    renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveCatlinksToBottom() {&lt;br /&gt;
    var main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
    var target = getCatlinksTarget();&lt;br /&gt;
    var catlinks = getCatlinkNodes();&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!main.length || !target.length) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    catlinks.forEach(function (node) {&lt;br /&gt;
        var catNode = $(node);&lt;br /&gt;
&lt;br /&gt;
        if (node.parentNode !== target[0]) {&lt;br /&gt;
            catNode.appendTo(target);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        normalizeCatlinksPanel(node);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (catlinks.length) return;&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        var cat;&lt;br /&gt;
&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (getCatlinkNodes().length) return;&lt;br /&gt;
        if (!categories || !categories.length) return;&lt;br /&gt;
&lt;br /&gt;
        cat = createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;);&lt;br /&gt;
        renderFetchedCatlinks(cat, categories, requestedPage);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 대문 스타일&lt;br /&gt;
function initCategoryNavIfAvailable(root) {&lt;br /&gt;
    /*&lt;br /&gt;
    CategoryNav.js는 대문 카테고리 네비를 SVG로 생성한다.&lt;br /&gt;
&lt;br /&gt;
    Common.js가 SPA로 본문을 갈아끼운 뒤에는 MediaWiki 원래 페이지 로드와 달리&lt;br /&gt;
    CategoryNav.js의 초기 DOMContentLoaded만으로는 새 mount를 다시 잡지 못할 수 있다.&lt;br /&gt;
    CategoryNav.js 자체도 mw.hook(&#039;wikipage.content&#039;)를 듣지만, 로드 순서와 SPA 타이밍이&lt;br /&gt;
    엇갈릴 수 있으므로 Common.js 쪽에서도 존재 여부를 확인한 뒤 한 번 더 호출한다.&lt;br /&gt;
&lt;br /&gt;
    이 함수는 CategoryNav.js가 아직 로드되지 않았으면 아무 것도 하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    if (&lt;br /&gt;
        window.CLBI &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI.categoryNav &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI.categoryNav.init === &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        window.CLBI.categoryNav.init(root || document);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeLegacyMainPageHero() {&lt;br /&gt;
    /*&lt;br /&gt;
    기존 대문 전용 레거시 요소 정리&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    이전 대문 구조에서는 Common.js가 본문 바깥에 #clbi-main-logo를 직접 삽입하고,&lt;br /&gt;
    본문 안의 #clbi-main-crt-hero를 #clbi-main-crt-hero-wrap으로 감싸서&lt;br /&gt;
    .liberty-content-main 위쪽으로 재배치했다.&lt;br /&gt;
&lt;br /&gt;
    새 대문은 본문 내부의 .main-portal이 로고, 알림, 카테고리 네비, 이미지 피드,&lt;br /&gt;
    방명록, 상태 패널을 모두 담당한다. 따라서 Common.js가 별도 로고나 CRT 래퍼를&lt;br /&gt;
    삽입하면 새 로고/콘텐츠와 중복된다.&lt;br /&gt;
&lt;br /&gt;
    여기서는 JS가 만들던 바깥 로고와 CRT 래퍼를 제거하고, 예전 대문 원본이나&lt;br /&gt;
    캐시된 렌더 결과에 남아 있을 수 있는 #clbi-main-crt-hero도 제거한다.&lt;br /&gt;
    */&lt;br /&gt;
    $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function setNativePageTitleHiddenHard(hidden) {&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    document.querySelectorAll(selectors.join(&#039;,&#039;)).forEach(function(node) {&lt;br /&gt;
        if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
        if (hidden) {&lt;br /&gt;
            node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;visibility&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;min-height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;margin&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;padding&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;overflow&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
        } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;min-height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;margin&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;padding&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyDefaultPageTitleVisibility() {&lt;br /&gt;
    var hideTitle = true;&lt;br /&gt;
    var isSystemAssetPage = false;&lt;br /&gt;
&lt;br /&gt;
    if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isSystemAssetPage === &#039;function&#039;) {&lt;br /&gt;
        isSystemAssetPage = window.CLBI_PAGE_SHELL.isSystemAssetPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage) {&lt;br /&gt;
        hideTitle = true;&lt;br /&gt;
    } else if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isBackendOrSystemPage === &#039;function&#039;) {&lt;br /&gt;
        hideTitle = !window.CLBI_PAGE_SHELL.isBackendOrSystemPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;)&lt;br /&gt;
        .toggleClass(&#039;page-title-hidden&#039;, hideTitle)&lt;br /&gt;
        .toggleClass(&#039;page-title-visible&#039;, !hideTitle)&lt;br /&gt;
        .toggleClass(&#039;clbi-system-doc-page&#039;, isSystemAssetPage);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.renderSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.renderSystemDocIndicator();&lt;br /&gt;
    } else if (!isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.removeSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.removeSystemDocIndicator();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (hideTitle) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(false);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyMainPageStyle() {&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    if (specialPage === &#039;Preferences&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var namespaceNumber = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var isMainPage = (pageName === &#039;대문&#039;);&lt;br /&gt;
    var isUserProfilePage = (namespaceNumber === 2);&lt;br /&gt;
    var isScreenDoc = ($(&#039;.screen-header&#039;).length &amp;gt; 0);&lt;br /&gt;
    var hideTools = (isMainPage || isUserProfilePage || !canShowContentTools());&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, isUserProfilePage);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;clbi-main-page&#039;, isMainPage);&lt;br /&gt;
&lt;br /&gt;
    // 모든 문서에서 분류 바를 본문 컨테이너 아래로 이동&lt;br /&gt;
    moveCatlinksToBottom();&lt;br /&gt;
&lt;br /&gt;
    if (isMainPage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 새 대문은 .main-portal 본문 구조가 로고/히어로를 담당한다.&lt;br /&gt;
        // Common.js의 구식 바깥 로고/CRT 재배치 루틴은 사용하지 않는다.&lt;br /&gt;
        removeLegacyMainPageHero();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    } else if (isUserProfilePage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else if (isScreenDoc) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        if ($(&#039;#clbi-tools-box&#039;).length === 0 &amp;amp;&amp;amp; canShowContentTools()) {&lt;br /&gt;
            var $toolsBox = $(&#039;&amp;lt;div id=&amp;quot;clbi-tools-box&amp;quot; class=&amp;quot;clbi-left-box&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsTitle = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;관리&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsContent = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-content&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
            $toolsContent.append($(&#039;.content-tools .btn-group&#039;).clone(true));&lt;br /&gt;
            $toolsBox.append($toolsTitle).append($toolsContent);&lt;br /&gt;
            $(&#039;#clbi-left-sidebar&#039;).append($toolsBox);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!isUserProfilePage) {&lt;br /&gt;
        $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
        $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    applyDefaultPageTitleVisibility();&lt;br /&gt;
    updateSidebar();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 본문 기본 목차 제거&lt;br /&gt;
function removeNativeTocFromContent() {&lt;br /&gt;
    $(&#039;.liberty-content-main #toc, .liberty-content-main .toc&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 ID 가져오기&lt;br /&gt;
function getHeadingId(heading) {&lt;br /&gt;
    if (heading.id) {&lt;br /&gt;
        return heading.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline[id]&#039;);&lt;br /&gt;
    if (headline &amp;amp;&amp;amp; headline.id) {&lt;br /&gt;
        return headline.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 제목 텍스트 가져오기&lt;br /&gt;
function getHeadingText(heading) {&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline&#039;);&lt;br /&gt;
    var source = headline || heading;&lt;br /&gt;
    var clone = source.cloneNode(true);&lt;br /&gt;
&lt;br /&gt;
    $(clone).find(&#039;.mw-editsection, .mw-editsection-bracket, .mw-editsection-divider&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    return (clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: 긴 제목에 자동 스크롤 적용&lt;br /&gt;
function initTocTitleScroll(root) {&lt;br /&gt;
    var $items = root&lt;br /&gt;
        ? $(root).find(&#039;.toc-scroll-text&#039;)&lt;br /&gt;
        : $(&#039;#side-toc-box .toc-scroll-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    $items.each(function () {&lt;br /&gt;
        var $text = $(this);&lt;br /&gt;
        var $wrap = $text.closest(&#039;.toc-scroll-wrap&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$wrap.length) return;&lt;br /&gt;
&lt;br /&gt;
        var wrapW = Math.floor($wrap.width());&lt;br /&gt;
        var textW = Math.ceil(this.scrollWidth);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 레이아웃 계산이 끝나지 않았으면 이번 실행에서는 건드리지 않는다.&lt;br /&gt;
        if (!wrapW || !textW) return;&lt;br /&gt;
&lt;br /&gt;
        if (textW &amp;lt;= wrapW + 12) {&lt;br /&gt;
            // 왼쪽 목차: 칸을 넘지 않는 제목은 전체 텍스트를 그대로 보여준다.&lt;br /&gt;
            $wrap.removeClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
            if ($text.data(&#039;toc-scroll-enabled&#039;)) {&lt;br /&gt;
                $text.css({&lt;br /&gt;
                    animation: &#039;&#039;,&lt;br /&gt;
                    &#039;animation-delay&#039;: &#039;&#039;,&lt;br /&gt;
                    &#039;--scroll-dist&#039;: &#039;&#039;&lt;br /&gt;
                });&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-enabled&#039;);&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-key&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var scrollDist = &#039;-&#039; + (textW - wrapW + 10) + &#039;px&#039;;&lt;br /&gt;
        var duration = Math.max(7, textW / 38) * 1.25;&lt;br /&gt;
        var scrollKey = scrollDist + &#039;|&#039; + duration;&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목에는 오른쪽 페이드와 스크롤을 적용한다.&lt;br /&gt;
        $wrap.addClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 같은 값으로 이미 적용된 애니메이션은 다시 초기화하지 않는다.&lt;br /&gt;
        if ($text.data(&#039;toc-scroll-key&#039;) === scrollKey) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $text.data(&#039;toc-scroll-enabled&#039;, true);&lt;br /&gt;
        $text.data(&#039;toc-scroll-key&#039;, scrollKey);&lt;br /&gt;
&lt;br /&gt;
        $text.css({&lt;br /&gt;
            // 왼쪽 목차: 페이지 진입 직후에는 잠시 읽을 시간을 준 뒤 흐르게 한다.&lt;br /&gt;
            animation: &#039;toc-scroll-blink-reset &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
            &#039;animation-delay&#039;: &#039;1s&#039;,&lt;br /&gt;
            &#039;--scroll-dist&#039;: scrollDist&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 목차를 왼쪽 사이드바에 새로 생성&lt;br /&gt;
function moveTocToLeftSidebar() {&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: MediaWiki가 만든 원래 목차는 본문에서 제거한다.&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
&lt;br /&gt;
    var leftSidebar = document.getElementById(&#039;clbi-left-sidebar&#039;);&lt;br /&gt;
    if (!leftSidebar) return;&lt;br /&gt;
&lt;br /&gt;
    var content =&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;) ||&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!content) return;&lt;br /&gt;
&lt;br /&gt;
    var headings = Array.prototype.slice.call(&lt;br /&gt;
        content.querySelectorAll(&#039;h2, h3&#039;)&lt;br /&gt;
    ).filter(function (heading) {&lt;br /&gt;
        if (heading.closest(&#039;#toc, .toc, #side-toc-box&#039;)) return false;&lt;br /&gt;
&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
&lt;br /&gt;
        if (!id || !text) return false;&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var tocKey = headings.map(function (heading) {&lt;br /&gt;
        return getHeadingId(heading) + &#039;|&#039; + getHeadingText(heading);&lt;br /&gt;
    }).join(&#039;||&#039;);&lt;br /&gt;
&lt;br /&gt;
    var existingBox = document.getElementById(&#039;side-toc-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 같은 문서에서 같은 목차를 이미 만들었다면 다시 지우고 만들지 않는다.&lt;br /&gt;
    if (existingBox &amp;amp;&amp;amp; existingBox.getAttribute(&#039;data-toc-key&#039;) === tocKey) {&lt;br /&gt;
        initTocTitleScroll(existingBox);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (existingBox) {&lt;br /&gt;
        existingBox.remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!headings.length) return;&lt;br /&gt;
&lt;br /&gt;
    var tocBox = document.createElement(&#039;div&#039;);&lt;br /&gt;
    tocBox.className = &#039;clbi-left-box&#039;;&lt;br /&gt;
    tocBox.id = &#039;side-toc-box&#039;;&lt;br /&gt;
    tocBox.setAttribute(&#039;data-toc-key&#039;, tocKey);&lt;br /&gt;
&lt;br /&gt;
    var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
    title.className = &#039;clbi-left-title&#039;;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 박스 제목은 Lang.js의 현재 UI 언어를 따른다.&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
    var tocTitleText = (t &amp;amp;&amp;amp; t.toc) ? t.toc : &#039;목차&#039;;&lt;br /&gt;
&lt;br /&gt;
    title.textContent = tocTitleText;&lt;br /&gt;
&lt;br /&gt;
    var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
    body.className = &#039;clbi-left-content toc-sidebar-content&#039;;&lt;br /&gt;
&lt;br /&gt;
    var list = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    list.className = &#039;generated-toc&#039;;&lt;br /&gt;
&lt;br /&gt;
    headings.forEach(function (heading) {&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
        var level = heading.tagName.toLowerCase() === &#039;h3&#039; ? 3 : 2;&lt;br /&gt;
&lt;br /&gt;
        var item = document.createElement(&#039;li&#039;);&lt;br /&gt;
        item.className = &#039;toc-level-&#039; + level;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.setAttribute(&#039;href&#039;, &#039;#&#039; + id);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목 스크롤을 위해 텍스트를 별도 span으로 감싼다.&lt;br /&gt;
        var textWrap = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textWrap.className = &#039;toc-scroll-wrap&#039;;&lt;br /&gt;
&lt;br /&gt;
        var textSpan = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textSpan.className = &#039;toc-scroll-text&#039;;&lt;br /&gt;
        textSpan.textContent = text;&lt;br /&gt;
&lt;br /&gt;
        textWrap.appendChild(textSpan);&lt;br /&gt;
        link.appendChild(textWrap);&lt;br /&gt;
&lt;br /&gt;
        item.appendChild(link);&lt;br /&gt;
        list.appendChild(item);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    body.appendChild(list);&lt;br /&gt;
    tocBox.appendChild(title);&lt;br /&gt;
    tocBox.appendChild(body);&lt;br /&gt;
    leftSidebar.appendChild(tocBox);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: DOM 배치가 끝난 뒤 긴 제목 스크롤 여부를 계산한다.&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        initTocTitleScroll(tocBox);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            initTocTitleScroll(tocBox);&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 우측 광고판: 이미지/번역 캡션 목록&lt;br /&gt;
var RIGHT_BILLBOARD_ITEMS = [&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
        alt: &#039;PROOF TO THE WORLD / YOU ONCE PART OF IT&#039;,&lt;br /&gt;
        duration: 3000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;당신이 한때 이 세계의&#039;,&lt;br /&gt;
            &#039;&#039;,&lt;br /&gt;
            &#039;일부였다는 것을 증명하십시오&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-003.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    }&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItem(index) {&lt;br /&gt;
    var items = RIGHT_BILLBOARD_ITEMS;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        return {&lt;br /&gt;
            file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
            alt: &#039;&#039;,&lt;br /&gt;
            caption: []&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var normalized = index % items.length;&lt;br /&gt;
    if (normalized &amp;lt; 0) normalized += items.length;&lt;br /&gt;
&lt;br /&gt;
    return items[normalized];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardImageUrl(fileName) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/&#039; + encodeURIComponent(fileName || &#039;Side-visual-001.png&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardCaptionHtml(item) {&lt;br /&gt;
    var lines = item &amp;amp;&amp;amp; item.caption ? item.caption : [];&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    function escapeCaptionText(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/\&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    lines.forEach(function(line) {&lt;br /&gt;
        var text = String(line == null ? &#039;&#039; : line);&lt;br /&gt;
        var isGap = !text.trim();&lt;br /&gt;
        var className = &#039;right-billboard-caption-line&#039; + (isGap ? &#039; is-gap&#039; : &#039;&#039;);&lt;br /&gt;
        html += &#039;&amp;lt;span class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; + (isGap ? &#039;&amp;amp;nbsp;&#039; : escapeCaptionText(text)) + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setRightBillboardItem(index) {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    var item = getRightBillboardItem(index);&lt;br /&gt;
    var src = getRightBillboardImageUrl(item.file);&lt;br /&gt;
    var images = box.querySelectorAll(&#039;.right-billboard-image&#039;);&lt;br /&gt;
    var caption = box.querySelector(&#039;#right-billboard-caption&#039;);&lt;br /&gt;
    var emptySub = box.querySelector(&#039;.right-billboard-empty-sub&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, String(index));&lt;br /&gt;
    box.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(images, function(img) {&lt;br /&gt;
        img.style.display = &#039;&#039;;&lt;br /&gt;
        img.setAttribute(&#039;src&#039;, src);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, img.classList.contains(&#039;right-billboard-image-base&#039;) ? (item.alt || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (caption) {&lt;br /&gt;
        caption.innerHTML = getRightBillboardCaptionHtml(item);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (emptySub) {&lt;br /&gt;
        emptySub.textContent = item.file || &#039;Side-visual-001.png&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItemDuration(item) {&lt;br /&gt;
    var duration = item &amp;amp;&amp;amp; item.duration ? parseInt(item.duration, 10) : 3000;&lt;br /&gt;
&lt;br /&gt;
    if (Number.isNaN(duration) || duration &amp;lt; 500) {&lt;br /&gt;
        duration = 3000;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return duration;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initRightBillboardCarousel() {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-billboard-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    setRightBillboardItem(0);&lt;br /&gt;
&lt;br /&gt;
    if (!RIGHT_BILLBOARD_ITEMS || RIGHT_BILLBOARD_ITEMS.length &amp;lt;= 1) return;&lt;br /&gt;
&lt;br /&gt;
    function scheduleNext() {&lt;br /&gt;
        var current = parseInt(box.getAttribute(&#039;data-billboard-index&#039;) || &#039;0&#039;, 10);&lt;br /&gt;
        if (Number.isNaN(current)) current = 0;&lt;br /&gt;
&lt;br /&gt;
        var currentItem = getRightBillboardItem(current);&lt;br /&gt;
        var delay = getRightBillboardItemDuration(currentItem);&lt;br /&gt;
&lt;br /&gt;
        window.setTimeout(function() {&lt;br /&gt;
            if (!document.body.contains(box)) return;&lt;br /&gt;
&lt;br /&gt;
            if (!document.hidden) {&lt;br /&gt;
                setRightBillboardItem(current + 1);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            scheduleNext();&lt;br /&gt;
        }, delay);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scheduleNext();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function escapeRightBillboardAttr(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildRightBillboardBox() {&lt;br /&gt;
    var billboardInitial = getRightBillboardItem(0);&lt;br /&gt;
    var billboardSrc = getRightBillboardImageUrl(billboardInitial.file);&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box right-billboard-box left-billboard-box left-ad-box&amp;quot; data-billboard-index=&amp;quot;0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title right-billboard-title left-ad-title left-ad-title-iconless&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-ad&amp;quot; class=&amp;quot;left-ad-title-label&amp;quot;&amp;gt;Looking for a job?&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content left-ad-content-shell&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;right-billboard-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-recess&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;right-billboard-screen&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;right-billboard-image&amp;quot; class=&amp;quot;right-billboard-image right-billboard-image-base&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&#039; + escapeRightBillboardAttr(billboardInitial.alt || &#039;&#039;) + &#039;&amp;quot; onload=&amp;quot;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.remove(\&#039;is-empty\&#039;);}&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.add(\&#039;is-empty\&#039;);}&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-image-bloom&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-a&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-b&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-c&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-glitch&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-tear&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-empty&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-main&amp;quot;&amp;gt;SIGNAL EMPTY&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-sub&amp;quot;&amp;gt;&#039; + escapeRightBillboardAttr(billboardInitial.file || &#039;Side-visual-001.png&#039;) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;right-billboard-caption&amp;quot; class=&amp;quot;right-billboard-caption&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + getRightBillboardCaptionHtml(billboardInitial) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-bottom-finish left-ad-bottom-finish&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var GREAT_WALL_DATA_TITLE = &#039;프로젝트:The_Great_Wall/Data.json&#039;;&lt;br /&gt;
var GREAT_WALL_LIST_LIMIT = 0;&lt;br /&gt;
var greatWallState = {&lt;br /&gt;
    data: { entries: {} },&lt;br /&gt;
    loaded: false,&lt;br /&gt;
    loading: false,&lt;br /&gt;
    saving: false,&lt;br /&gt;
    selectedOwnEntry: false,&lt;br /&gt;
    statusText: &#039;&#039;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function normalizeGreatWallData(data) {&lt;br /&gt;
    var normalized = { entries: {} };&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    Object.keys(entries).forEach(function(key) {&lt;br /&gt;
        var item = entries[key];&lt;br /&gt;
        var user = item &amp;amp;&amp;amp; item.user ? String(item.user) : String(key || &#039;&#039;);&lt;br /&gt;
        var text = item &amp;amp;&amp;amp; item.text ? String(item.text) : &#039;&#039;;&lt;br /&gt;
        var timestamp = item &amp;amp;&amp;amp; item.timestamp ? String(item.timestamp) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!user || !text) return;&lt;br /&gt;
&lt;br /&gt;
        normalized.entries[user] = {&lt;br /&gt;
            user: user,&lt;br /&gt;
            text: text.slice(0, 140),&lt;br /&gt;
            timestamp: timestamp || new Date(0).toISOString()&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseGreatWallData(text) {&lt;br /&gt;
    var parsed;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;The Great Wall data parse failed:&#039;, err);&lt;br /&gt;
        parsed = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return normalizeGreatWallData(parsed);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function stringifyGreatWallData(data) {&lt;br /&gt;
    return JSON.stringify(normalizeGreatWallData(data), null, 2) + &#039;\n&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallRevisionText(page) {&lt;br /&gt;
    var rev;&lt;br /&gt;
    var slot;&lt;br /&gt;
&lt;br /&gt;
    if (!page || !page.revisions || !page.revisions.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    rev = page.revisions[0];&lt;br /&gt;
&lt;br /&gt;
    if (rev.slots &amp;amp;&amp;amp; rev.slots.main) {&lt;br /&gt;
        slot = rev.slots.main;&lt;br /&gt;
        return slot.content || slot[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return rev.content || rev[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchGreatWallData() {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
    return api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;revisions&#039;,&lt;br /&gt;
        titles: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
        rvprop: &#039;content|timestamp&#039;,&lt;br /&gt;
        rvslots: &#039;main&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages[0] || null;&lt;br /&gt;
&lt;br /&gt;
        if (!page || page.missing) {&lt;br /&gt;
            return { entries: {} };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parseGreatWallData(getGreatWallRevisionText(page));&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall load failed:&#039;, err);&lt;br /&gt;
        return { entries: {} };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallEntries(data) {&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    return Object.keys(entries).map(function(key) {&lt;br /&gt;
        return entries[key];&lt;br /&gt;
    }).filter(function(item) {&lt;br /&gt;
        return item &amp;amp;&amp;amp; item.user &amp;amp;&amp;amp; item.text;&lt;br /&gt;
    }).sort(function(a, b) {&lt;br /&gt;
        return String(a.timestamp || &#039;&#039;).localeCompare(String(b.timestamp || &#039;&#039;));&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallMessageTime(timestamp) {&lt;br /&gt;
    var date = timestamp ? new Date(timestamp) : null;&lt;br /&gt;
    var month;&lt;br /&gt;
    var day;&lt;br /&gt;
    var hour;&lt;br /&gt;
    var minute;&lt;br /&gt;
&lt;br /&gt;
    if (!date || isNaN(date.getTime())) return &#039;—&#039;;&lt;br /&gt;
&lt;br /&gt;
    month = String(date.getMonth() + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
    day = String(date.getDate()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    hour = String(date.getHours()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    minute = String(date.getMinutes()).padStart(2, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    return month + &#039;.&#039; + day + &#039; &#039; + hour + &#039;:&#039; + minute;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallAvatarSrc(user) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(String(user || &#039;&#039;)) + &#039;.png&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallEntryHtml(item, currentUser, selectedOwnEntry) {&lt;br /&gt;
    var isOwn = currentUser &amp;amp;&amp;amp; item.user === currentUser;&lt;br /&gt;
    var tag = isOwn ? &#039;button&#039; : &#039;div&#039;;&lt;br /&gt;
    var attrs = isOwn ? &#039; type=&amp;quot;button&amp;quot; data-great-wall-own-entry=&amp;quot;1&amp;quot; aria-label=&amp;quot;Edit your wall message&amp;quot;&#039; : &#039;&#039;;&lt;br /&gt;
    var className = &#039;great-wall-entry&#039; + (isOwn ? &#039; is-own&#039; : &#039;&#039;) + (isOwn &amp;amp;&amp;amp; selectedOwnEntry ? &#039; is-selected&#039; : &#039;&#039;);&lt;br /&gt;
    var avatarSrc = getGreatWallAvatarSrc(item.user);&lt;br /&gt;
    var messageTime = getGreatWallMessageTime(item.timestamp);&lt;br /&gt;
    var isoTime = item.timestamp || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;&#039; + tag + attrs + &#039; class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;img class=&amp;quot;great-wall-avatar&amp;quot; src=&amp;quot;&#039; + escapeClbiHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=&amp;amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;quot;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-bubble&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-entry-head&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-user&amp;quot;&amp;gt;@&#039; + escapeClbiHtml(item.user) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-time&amp;quot; title=&amp;quot;&#039; + escapeClbiHtml(isoTime) + &#039;&amp;quot;&amp;gt;&#039; + escapeClbiHtml(messageTime) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-text&amp;quot;&amp;gt;&#039; + escapeClbiHtml(item.text) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/&#039; + tag + &#039;&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var list = document.getElementById(&#039;great-wall-list&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
    var status = document.getElementById(&#039;great-wall-status&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var entries = getGreatWallEntries(greatWallState.data);&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var visibleEntries = [];&lt;br /&gt;
    var seen = {};&lt;br /&gt;
&lt;br /&gt;
    if (!box || !list) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.toggle(&#039;is-guest&#039;, !currentUser);&lt;br /&gt;
    box.classList.toggle(&#039;is-loading&#039;, !!greatWallState.loading);&lt;br /&gt;
    box.classList.toggle(&#039;is-saving&#039;, !!greatWallState.saving);&lt;br /&gt;
    box.classList.toggle(&#039;has-own-entry&#039;, !!ownEntry);&lt;br /&gt;
    box.classList.toggle(&#039;is-own-selected&#039;, !!greatWallState.selectedOwnEntry);&lt;br /&gt;
&lt;br /&gt;
    entries.forEach(function(item) {&lt;br /&gt;
        if (seen[item.user]) return;&lt;br /&gt;
        visibleEntries.push(item);&lt;br /&gt;
        seen[item.user] = true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.loading &amp;amp;&amp;amp; !greatWallState.loaded) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else if (!visibleEntries.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;NO MARKS&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else {&lt;br /&gt;
        list.innerHTML = visibleEntries.map(function(item) {&lt;br /&gt;
            return buildGreatWallEntryHtml(item, currentUser, greatWallState.selectedOwnEntry);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list &amp;amp;&amp;amp; list.scrollHeight &amp;gt; list.clientHeight) {&lt;br /&gt;
        list.scrollTop = list.scrollHeight;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (status) {&lt;br /&gt;
        // Status text is intentionally suppressed in the composer strip; the area is spacing-only UI.&lt;br /&gt;
        status.textContent = &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!input || !submit) return;&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        submit.disabled = false;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = &#039;Login required&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.saving || greatWallState.loading) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = greatWallState.saving ? &#039;SAVE&#039; : &#039;SYNC&#039;;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = greatWallState.saving ? &#039;Saving&#039; : &#039;Syncing&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.disabled = !ownEntry;&lt;br /&gt;
        deleteButton.title = ownEntry ? &#039;Delete your mark&#039; : &#039;No mark to delete&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        input.disabled = false;&lt;br /&gt;
        input.readOnly = true;&lt;br /&gt;
        input.setAttribute(&#039;aria-readonly&#039;, &#039;true&#039;);&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = &#039;UPDATE&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
    input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; greatWallState.selectedOwnEntry &amp;amp;&amp;amp; !input.value) {&lt;br /&gt;
        input.value = ownEntry.text || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    submit.disabled = false;&lt;br /&gt;
    submit.textContent = ownEntry ? &#039;UPDATE&#039; : &#039;POST&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function saveGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var text = input ? String(input.value || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!text) {&lt;br /&gt;
        greatWallState.statusText = &#039;EMPTY MARK&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (text.length &amp;gt; 140) {&lt;br /&gt;
        text = text.slice(0, 140);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SAVING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        data.entries[currentUser] = {&lt;br /&gt;
            user: currentUser,&lt;br /&gt;
            text: text,&lt;br /&gt;
            timestamp: new Date().toISOString()&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Update The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK UPDATED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall save failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;SAVE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function deleteGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!ownEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;NO MARK TO DELETE&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;DELETING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        if (data.entries &amp;amp;&amp;amp; data.entries[currentUser]) {&lt;br /&gt;
            delete data.entries[currentUser];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Delete The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK DELETED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall delete failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;DELETE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBoxWhenReady() {&lt;br /&gt;
    if (!document.getElementById(&#039;great-wall-sidebar&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    if (mw.Api) {&lt;br /&gt;
        initGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.loader &amp;amp;&amp;amp; mw.loader.using) {&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
            initGreatWallBox();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-great-wall-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-great-wall-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var ownButton = e.target.closest ? e.target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;]&#039;) : null;&lt;br /&gt;
        var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
        var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
&lt;br /&gt;
        if (!ownButton || !ownEntry) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = true;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
        if (input) {&lt;br /&gt;
            input.focus();&lt;br /&gt;
            input.setSelectionRange(input.value.length, input.value.length);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var target = e.target;&lt;br /&gt;
        var keepSelection;&lt;br /&gt;
&lt;br /&gt;
        if (!greatWallState.selectedOwnEntry || !target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
        keepSelection = target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;], .great-wall-editor, .great-wall-compose-sector&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (keepSelection) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        if (input) input.value = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (submit) {&lt;br /&gt;
        submit.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
                window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            saveGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            deleteGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (input) {&lt;br /&gt;
        input.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
            if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
                e.preventDefault();&lt;br /&gt;
                saveGreatWallEntry();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&#039;input&#039;, function() {&lt;br /&gt;
            if (input.value.length &amp;gt; 140) {&lt;br /&gt;
                input.value = input.value.slice(0, 140);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.loading = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SYNCING WALL&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        greatWallState.data = normalizeGreatWallData(data);&lt;br /&gt;
        greatWallState.loaded = true;&lt;br /&gt;
        greatWallState.loading = false;&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;great-wall-sidebar&amp;quot; class=&amp;quot;clbi-right-box great-wall-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title great-wall-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-great-wall&amp;quot;&amp;gt;The Great Wall&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content great-wall-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-list&amp;quot; class=&amp;quot;great-wall-list&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-compose-sector&amp;quot; aria-label=&amp;quot;The Great Wall editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;input id=&amp;quot;great-wall-input&amp;quot; class=&amp;quot;great-wall-input&amp;quot; type=&amp;quot;text&amp;quot; maxlength=&amp;quot;140&amp;quot; autocomplete=&amp;quot;off&amp;quot; placeholder=&amp;quot;담벼락&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-submit&amp;quot; class=&amp;quot;great-wall-submit&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;POST&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-delete&amp;quot; class=&amp;quot;great-wall-delete&amp;quot; type=&amp;quot;button&amp;quot; disabled&amp;gt;DEL&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-status&amp;quot; class=&amp;quot;great-wall-status&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeMainPortalGuestbookPreview() {&lt;br /&gt;
    var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    var guestbook = portal ? portal.querySelector(&#039;.guestbook-device&#039;) : null;&lt;br /&gt;
    var sideScreen;&lt;br /&gt;
    var grid;&lt;br /&gt;
&lt;br /&gt;
    if (!portal || !guestbook) return;&lt;br /&gt;
&lt;br /&gt;
    sideScreen = guestbook.closest ? guestbook.closest(&#039;.side-screen&#039;) : null;&lt;br /&gt;
    grid = sideScreen &amp;amp;&amp;amp; sideScreen.closest ? sideScreen.closest(&#039;.console-grid&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (guestbook.parentNode) {&lt;br /&gt;
        guestbook.parentNode.removeChild(guestbook);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sideScreen &amp;amp;&amp;amp; !(sideScreen.textContent || &#039;&#039;).trim() &amp;amp;&amp;amp; !sideScreen.querySelector(&#039;img,svg,video,canvas,form,input,button,a&#039;)) {&lt;br /&gt;
        if (sideScreen.parentNode) {&lt;br /&gt;
            sideScreen.parentNode.removeChild(sideScreen);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (grid) {&lt;br /&gt;
            grid.style.gridTemplateColumns = &#039;minmax(0,1fr)&#039;;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    portal.classList.add(&#039;is-great-wall-relocated&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildSiteInformationBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-right-box site-info-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title site-info-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span&amp;gt;정보&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content site-info-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;policy-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/개인정보처리방침&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;개인정보처리방침&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/면책_조항&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;면책 조항&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/라이선스&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;라이선스&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/크레딧&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;크레딧&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;social-strip&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://discord.gg/ctaeJ9d3Q5&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;DC&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.youtube.com/@nxdsxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;YT&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://x.com/nxd_sxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;X&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;WIP:&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 초기화 함수&lt;br /&gt;
function initSidebars() {&lt;br /&gt;
    var header = $(&#039;.liberty-content-header&#039;);&lt;br /&gt;
    var content = $(&#039;.liberty-content&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (header.length &amp;amp;&amp;amp; content.length) {&lt;br /&gt;
        header.prependTo(content);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-right-sidebar&#039;).length === 0) {&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        var isLoggedIn = username !== null;&lt;br /&gt;
        var avatarSrc = isLoggedIn&lt;br /&gt;
            ? &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + username + &#039;.png&#039;&lt;br /&gt;
            : &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&#039;;&lt;br /&gt;
&lt;br /&gt;
        var userBox;&lt;br /&gt;
&lt;br /&gt;
        if (isLoggedIn) {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a href=&amp;quot;/index.php/사용자:&#039; + username + &#039;&amp;quot; id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;&#039; + username + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-quick-actions&amp;quot; aria-label=&amp;quot;프로필 빠른 메뉴&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-inventory&amp;quot; aria-label=&amp;quot;인벤토리&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_PACKAGE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;인벤토리&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-achievements&amp;quot; aria-label=&amp;quot;업적&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_TROPHY + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;업적&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-notifications&amp;quot; aria-label=&amp;quot;알림&amp;quot;&amp;gt;&amp;lt;span id=&amp;quot;profile-quick-notification-icon&amp;quot; class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_BELL + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;알림&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:기여/&#039; + username + &#039;&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-contribution&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_TEXT + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;기여&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:주시문서목록&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-watchlist&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_EYE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;주시문서 목록&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:설정&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-preferences&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SETTINGS + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;설정&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그아웃&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn clbi-user-btn-logout&amp;quot; id=&amp;quot;clbi-btn-logout&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그아웃&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row profile-name-row-guest&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;Guest&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그인&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-login&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그인&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        var greatWallBox = &#039;&#039;;&lt;br /&gt;
        var siteInformationBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            greatWallBox = buildGreatWallBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;The Great Wall build failed:&#039;, err);&lt;br /&gt;
            greatWallBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            siteInformationBox = buildSiteInformationBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Site information build failed:&#039;, err);&lt;br /&gt;
            siteInformationBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        var sidebar = userBox + greatWallBox + siteInformationBox;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).append(&#039;&amp;lt;div id=&amp;quot;clbi-right-sidebar&amp;quot;&amp;gt;&#039; + sidebar + &#039;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        removeMainPortalGuestbookPreview();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    initGreatWallBoxWhenReady();&lt;br /&gt;
    removeMainPortalGuestbookPreview();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-left-sidebar&#039;).length === 0) {&lt;br /&gt;
var leftBillboardBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            leftBillboardBox = buildRightBillboardBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Left billboard build failed:&#039;, err);&lt;br /&gt;
            leftBillboardBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
var leftSidebar =&lt;br /&gt;
    &#039;&amp;lt;div id=&amp;quot;clbi-left-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-language&amp;quot;&amp;gt;언어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content sidebar-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selector&amp;quot; class=&amp;quot;sidebar-lang-selector sidebar-lang-dial&amp;quot; tabindex=&amp;quot;0&amp;quot; role=&amp;quot;group&amp;quot; aria-label=&amp;quot;언어 선택&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-dial-stage&amp;quot; class=&amp;quot;sidebar-lang-dial-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-fan&amp;quot; class=&amp;quot;sidebar-lang-fan&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selected-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-left&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-selected-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;한국어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-availability-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-right is-current&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-availability-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;CURRENT&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-sidebar-lang-apply&amp;quot; class=&amp;quot;sidebar-lang-apply&amp;quot; aria-label=&amp;quot;언어 적용&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;sidebar-lang-apply-mark&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;✓&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-news&amp;quot;&amp;gt;뉴스&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content clbi-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-title&amp;quot;&amp;gt;CHANGELOG&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-changelog-feed&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;a href=&amp;quot;/index.php/체인지로그&amp;quot; class=&amp;quot;news-post-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-post-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-post-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-main&amp;quot;&amp;gt;체인지로그&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-post-jump&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-divider&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-recent-title&amp;quot;&amp;gt;RECENT CHANGES&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-recent-feed&amp;quot; id=&amp;quot;clbi-left-recent-list&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;a class=&amp;quot;news-fill-image-slot&amp;quot; id=&amp;quot;clbi-left-news-fill-image&amp;quot; href=&amp;quot;/index.php/특수:최근바뀜&amp;quot; aria-label=&amp;quot;최근 바뀜으로 이동&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-fill-image-frame&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-fill-image&amp;quot; style=&amp;quot;--news-fill-image-url:url(\&#039;/index.php?title=특수:Redirect/file/Side-news-fill-001.png\&#039;);&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;img class=&amp;quot;news-fill-image-probe&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Side-news-fill-001.png&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.closest(\&#039;.news-fill-image-slot\&#039;).classList.add(\&#039;is-empty\&#039;);this.remove();&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        leftBillboardBox +&lt;br /&gt;
    &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).prepend(leftSidebar);&lt;br /&gt;
&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
        loadRecentChangesList(&#039;#clbi-left-recent-list&#039;, 10);&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
        updateLeftSidebarNationsImage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        initRightBillboardCarousel();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;Right billboard carousel failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
    applyMainPageStyle();&lt;br /&gt;
    initClbiCustomDocumentScrollbars();&lt;br /&gt;
    initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
        window.ProgressSystemWebUi.boot(&#039;initSidebars&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initNotifications();&lt;br /&gt;
            initProfile();&lt;br /&gt;
            moveTocToLeftSidebar();&lt;br /&gt;
        }, 300);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 800);&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 1500);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    loadLangScript(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initSidebars();&lt;br /&gt;
        }, 100);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click.profileQuickPlaceholder&#039;, &#039;#profile-quick-inventory, #profile-quick-achievements&#039;, function(e) {&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
function extractJsonArrayAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var depth = 0;&lt;br /&gt;
    var inString = false;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = String(text || &#039;&#039;).indexOf(&#039;[&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (inString) {&lt;br /&gt;
            if (escaped) {&lt;br /&gt;
                escaped = false;&lt;br /&gt;
            } else if (ch === &#039;\\&#039;) {&lt;br /&gt;
                escaped = true;&lt;br /&gt;
            } else if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
                inString = false;&lt;br /&gt;
            }&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            inString = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;[&#039;) depth += 1;&lt;br /&gt;
        if (ch === &#039;]&#039;) {&lt;br /&gt;
            depth -= 1;&lt;br /&gt;
            if (depth === 0) {&lt;br /&gt;
                try {&lt;br /&gt;
                    return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    return null;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function extractJsonStringAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var colon;&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    colon = text.indexOf(&#039;:&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (colon === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = text.indexOf(&#039;&amp;quot;&#039;, colon + 1);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start + 1; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (escaped) {&lt;br /&gt;
            escaped = false;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;\\&#039;) {&lt;br /&gt;
            escaped = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                return text.slice(start + 1, i);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function syncCatlinksConfigFromSpaDocument(doc) {&lt;br /&gt;
    var scripts = doc ? doc.querySelectorAll(&#039;script&#039;) : [];&lt;br /&gt;
    var categories = null;&lt;br /&gt;
    var hiddenCategories = null;&lt;br /&gt;
    var relevantPageName = null;&lt;br /&gt;
    var pageName = null;&lt;br /&gt;
    var i;&lt;br /&gt;
    var text;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; scripts.length; i += 1) {&lt;br /&gt;
        text = scripts[i].textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (categories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) categories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (hiddenCategories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgHiddenCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) hiddenCategories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (relevantPageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgRelevantPageName&#039;);&lt;br /&gt;
            if (value !== null) relevantPageName = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgPageName&#039;);&lt;br /&gt;
            if (value !== null) pageName = value;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.config.set(&#039;wgCategories&#039;, Array.isArray(categories) ? categories : []);&lt;br /&gt;
    mw.config.set(&#039;wgHiddenCategories&#039;, Array.isArray(hiddenCategories) ? hiddenCategories : []);&lt;br /&gt;
&lt;br /&gt;
    if (relevantPageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, relevantPageName);&lt;br /&gt;
    } else if (pageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    CLBI_CATLINKS_FETCH_TOKEN += 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// SPA 네비게이션&lt;br /&gt;
function shouldSkip(url) {&lt;br /&gt;
    return url.match(/action=edit|action=submit|action=history|action=delete|action=protect|action=purge|특수:로그인|특수:로그아웃|Special:UserLogin|Special:UserLogout|특수:사용자정보|특수:비밀번호바꾸기|uselang=/);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    if (window._spaInitialized) return;&lt;br /&gt;
    window._spaInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    function isInternal(url) {&lt;br /&gt;
        var a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = url;&lt;br /&gt;
        return a.hostname === window.location.hostname;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getCachedSpaPageHtml(url) {&lt;br /&gt;
        if (!window.EntryStore || typeof window.EntryStore.getTextSync !== &#039;function&#039;) return &#039;&#039;;&lt;br /&gt;
        return window.EntryStore.getTextSync(url) || window.EntryStore.getTextSync(String(url || &#039;&#039;).replace(/^https?:\/\/[^/]+/i, &#039;&#039;)) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchSpaPageHtml(url) {&lt;br /&gt;
        var cached = getCachedSpaPageHtml(url);&lt;br /&gt;
        if (cached) return Promise.resolve(cached);&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; }).then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDetachedEntryContent(newContent) {&lt;br /&gt;
        /*&lt;br /&gt;
        Initial boot prepares entry artifacts; SPA is only allowed to consume them.&lt;br /&gt;
        This hook runs while the fetched page is still detached, before the user sees it.&lt;br /&gt;
        It must stay synchronous or already-resolved: if a subsystem cannot prepare from&lt;br /&gt;
        EntryStore immediately, it should leave the old fallback path in place instead of&lt;br /&gt;
        opening BootGate during SPA.&lt;br /&gt;
        */&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.prepareContentForEntry === &#039;function&#039;) {&lt;br /&gt;
                window.NationsPanel.prepareContentForEntry(newContent);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;entry content preparation failed:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadPage(url) {&lt;br /&gt;
        invalidateProfileRender();&lt;br /&gt;
&lt;br /&gt;
        return fetchSpaPageHtml(url)&lt;br /&gt;
            .then(function(html) {&lt;br /&gt;
                var parser = new DOMParser();&lt;br /&gt;
                var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
                var scripts = doc.querySelectorAll(&#039;script&#039;);&lt;br /&gt;
                for (var i = 0; i &amp;lt; scripts.length; i++) {&lt;br /&gt;
                    var src = scripts[i].textContent;&lt;br /&gt;
&lt;br /&gt;
                    if (src.indexOf(&#039;wgNamespaceNumber&#039;) !== -1) {&lt;br /&gt;
                        var match = src.match(/&amp;quot;wgNamespaceNumber&amp;quot;:(-?\d+)/);&lt;br /&gt;
                        if (match) mw.config.set(&#039;wgNamespaceNumber&#039;, parseInt(match[1], 10));&lt;br /&gt;
&lt;br /&gt;
                        var matchTitle = src.match(/&amp;quot;wgTitle&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchTitle) mw.config.set(&#039;wgTitle&#039;, matchTitle[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchPage = src.match(/&amp;quot;wgPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchPage) mw.config.set(&#039;wgPageName&#039;, matchPage[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchArticle = src.match(/&amp;quot;wgArticleId&amp;quot;:(\d+)/);&lt;br /&gt;
                        if (matchArticle) {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, parseInt(matchArticle[1], 10));&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, 0);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchIsMainPage = src.match(/&amp;quot;wgIsMainPage&amp;quot;:(true|false)/);&lt;br /&gt;
                        if (matchIsMainPage) {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, matchIsMainPage[1] === &#039;true&#039;);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchSpecial = src.match(/&amp;quot;wgCanonicalSpecialPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchSpecial) {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, matchSpecial[1]);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                syncCatlinksConfigFromSpaDocument(doc);&lt;br /&gt;
&lt;br /&gt;
                var newContent = doc.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
                var newTitle = doc.querySelector(&#039;.mw-page-title-main&#039;);&lt;br /&gt;
                var newHead = doc.querySelector(&#039;title&#039;);&lt;br /&gt;
                var newHeader = doc.querySelector(&#039;.liberty-content-header&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (newContent) {&lt;br /&gt;
                    prepareDetachedEntryContent(newContent);&lt;br /&gt;
                    prepareSpaCatlinksBeforeInsert(newContent);&lt;br /&gt;
                    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
                    $(&#039;.liberty-content-main&#039;).html(newContent.innerHTML);&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    try {&lt;br /&gt;
                        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.renderPrepared === &#039;function&#039;) window.Decorations.renderPrepared();&lt;br /&gt;
                        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.renderPrepared === &#039;function&#039;) window.CLBI_DECORATIONS.renderPrepared();&lt;br /&gt;
                    } catch (err) {}&lt;br /&gt;
                    $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newTitle) {&lt;br /&gt;
                    $(&#039;.mw-page-title-main&#039;).html(newTitle.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHead) {&lt;br /&gt;
                    document.title = newHead.textContent;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHeader) {&lt;br /&gt;
                    $(&#039;.liberty-content-header&#039;).html(newHeader.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
                window.scrollTo(0, 0);&lt;br /&gt;
                mw.hook(&#039;wikipage.content&#039;).fire($(&#039;.liberty-content-main&#039;));&lt;br /&gt;
                applyMainPageStyle();&lt;br /&gt;
                initClbiCustomDocumentScrollbars();&lt;br /&gt;
                initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
                if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.handleSpaPageView === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.handleSpaPageView();&lt;br /&gt;
                } else if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.boot(&#039;spa&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 100);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 500);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 1200);&lt;br /&gt;
&lt;br /&gt;
                mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
                    initProfile();&lt;br /&gt;
                    moveTocToLeftSidebar();&lt;br /&gt;
                });&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function (err) {&lt;br /&gt;
                console.error(&#039;SPA page load failed:&#039;, err);&lt;br /&gt;
                $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
// 목차 링크는 전용 처리&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;#side-toc-box a, #toc a, .toc a&#039;, function(e) {&lt;br /&gt;
    var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
    if (!href || href.charAt(0) !== &#039;#&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var rawId = href.slice(1);&lt;br /&gt;
    if (!rawId) return;&lt;br /&gt;
&lt;br /&gt;
    var decodedId = rawId;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        decodedId = decodeURIComponent(rawId);&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        decodedId = rawId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var target = document.getElementById(decodedId);&lt;br /&gt;
&lt;br /&gt;
    if (!target &amp;amp;&amp;amp; window.CSS &amp;amp;&amp;amp; CSS.escape) {&lt;br /&gt;
        target = document.querySelector(&#039;#&#039; + CSS.escape(decodedId));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!target) return;&lt;br /&gt;
&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
    var scrollTarget = target.closest(&#039;h2, h3&#039;) || target;&lt;br /&gt;
&lt;br /&gt;
    scrollTarget.scrollIntoView({&lt;br /&gt;
        behavior: &#039;auto&#039;,&lt;br /&gt;
        block: &#039;start&#039;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    history.replaceState(null, &#039;&#039;, &#039;#&#039; + rawId);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
    /* 길라잡이는 문서 링크가 아니라 대문 본문 화면 탭이다. */&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;, function(e) {&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.toggle === &#039;function&#039;) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopImmediatePropagation();&lt;br /&gt;
            window.BottomGuideNav.toggle();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;a&#039;, function(e) {&lt;br /&gt;
        // 휠 클릭, 새 탭 열기, 보조키 이동은 브라우저 기본 동작을 유지한다.&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        /* 길라잡이 화면 탭은 위 전용 처리 외에는 SPA 문서 이동 대상으로 삼지 않는다. */&lt;br /&gt;
        if ($(this).is(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
        if (!href) return;&lt;br /&gt;
&lt;br /&gt;
        // 목차 링크는 별도 핸들러에서 처리&lt;br /&gt;
        if ($(this).closest(&#039;#side-toc-box, #toc, .toc&#039;).length) return;&lt;br /&gt;
&lt;br /&gt;
        // 단순 해시 링크는 SPA 가로채기 제외&lt;br /&gt;
        if (href.startsWith(&#039;#&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.href = href;&lt;br /&gt;
&lt;br /&gt;
        var samePath = decodeURIComponent(link.pathname) === decodeURIComponent(window.location.pathname);&lt;br /&gt;
        var sameSearch = (link.search || &#039;&#039;) === (window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; samePath &amp;amp;&amp;amp; sameSearch) return;&lt;br /&gt;
&lt;br /&gt;
        var currentBase = window.location.href.split(&#039;#&#039;)[0];&lt;br /&gt;
        var targetBase = link.href.split(&#039;#&#039;)[0];&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; currentBase === targetBase) return;&lt;br /&gt;
&lt;br /&gt;
        if (!isInternal(href)) return;&lt;br /&gt;
        if (shouldSkip(href)) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        playStaticSound();&lt;br /&gt;
        /*&lt;br /&gt;
        SPA must remain a consumer phase.  If the target page HTML was prepared by&lt;br /&gt;
        the first-load boot pack, do not show the legacy page-loading veil.  The&lt;br /&gt;
        route will consume cached HTML and detached entry artifacts before insertion.&lt;br /&gt;
        */&lt;br /&gt;
        if (getCachedSpaPageHtml(href)) {&lt;br /&gt;
            $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;body&#039;).addClass(&#039;page-loading&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        history.pushState(null, &#039;&#039;, href);&lt;br /&gt;
        loadPage(href);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;popstate&#039;, function() {&lt;br /&gt;
        loadPage(window.location.href);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ========== CLBI Custom Document Scrollbar ========== */&lt;br /&gt;
function isGeneralDocumentView() {&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    if (!body) return false;&lt;br /&gt;
&lt;br /&gt;
    return body.classList.contains(&#039;action-view&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-main-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-system-doc-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;backend-system-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiDocumentScrollTargets() {&lt;br /&gt;
    if (!isGeneralDocumentView()) return [];&lt;br /&gt;
&lt;br /&gt;
    return Array.prototype.slice.call(document.querySelectorAll(&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; #mw-content-text .mw-parser-output, &#039; +&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; .mw-body-content .mw-parser-output&#039;&lt;br /&gt;
    )).filter(function (el, index, list) {&lt;br /&gt;
        return el &amp;amp;&amp;amp; list.indexOf(el) === index;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiOuterWellForScroll(scrollEl) {&lt;br /&gt;
    var main = scrollEl ? scrollEl.closest(&#039;.liberty-content-main&#039;) : null;&lt;br /&gt;
    var children;&lt;br /&gt;
    var i;&lt;br /&gt;
    var child;&lt;br /&gt;
&lt;br /&gt;
    if (!main) return null;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(main.children || []);&lt;br /&gt;
    for (i = 0; i &amp;lt; children.length; i += 1) {&lt;br /&gt;
        child = children[i];&lt;br /&gt;
        if (&lt;br /&gt;
            child &amp;amp;&amp;amp;&lt;br /&gt;
            (child.id === &#039;mw-content-text&#039; || child.classList.contains(&#039;mw-body-content&#039;)) &amp;amp;&amp;amp;&lt;br /&gt;
            child.contains(scrollEl)&lt;br /&gt;
        ) {&lt;br /&gt;
            return child;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return scrollEl.parentElement || null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiCustomScrollbar(well, scrollEl) {&lt;br /&gt;
    var bar = well.querySelector(&#039;:scope &amp;gt; .clbi-custom-scrollbar&#039;);&lt;br /&gt;
    var up;&lt;br /&gt;
    var track;&lt;br /&gt;
    var thumb;&lt;br /&gt;
    var down;&lt;br /&gt;
&lt;br /&gt;
    if (!bar) {&lt;br /&gt;
        bar = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bar.className = &#039;clbi-custom-scrollbar&#039;;&lt;br /&gt;
        bar.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        bar.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-up&amp;quot; data-scroll-arrow=&amp;quot;up&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-track&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;clbi-custom-scroll-thumb&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-down&amp;quot; data-scroll-arrow=&amp;quot;down&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        well.appendChild(bar);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.__clbiScrollTarget = scrollEl;&lt;br /&gt;
    up = bar.querySelector(&#039;.clbi-custom-scroll-arrow-up&#039;);&lt;br /&gt;
    track = bar.querySelector(&#039;.clbi-custom-scroll-track&#039;);&lt;br /&gt;
    thumb = bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;);&lt;br /&gt;
    down = bar.querySelector(&#039;.clbi-custom-scroll-arrow-down&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (up &amp;amp;&amp;amp; !up.__clbiBound) {&lt;br /&gt;
        up.__clbiBound = true;&lt;br /&gt;
        up.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop -= 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (down &amp;amp;&amp;amp; !down.__clbiBound) {&lt;br /&gt;
        down.__clbiBound = true;&lt;br /&gt;
        down.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop += 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (track &amp;amp;&amp;amp; !track.__clbiBound) {&lt;br /&gt;
        track.__clbiBound = true;&lt;br /&gt;
        track.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var rect;&lt;br /&gt;
            var thumbRect;&lt;br /&gt;
            var target;&lt;br /&gt;
            var direction;&lt;br /&gt;
&lt;br /&gt;
            if (e.target === thumb) return;&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            target = bar.__clbiScrollTarget;&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            rect = track.getBoundingClientRect();&lt;br /&gt;
            thumbRect = thumb.getBoundingClientRect();&lt;br /&gt;
            direction = e.clientY &amp;lt; thumbRect.top ? -1 : 1;&lt;br /&gt;
            target.scrollTop += direction * Math.max(60, Math.floor(target.clientHeight * 0.82));&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (thumb &amp;amp;&amp;amp; !thumb.__clbiBound) {&lt;br /&gt;
        thumb.__clbiBound = true;&lt;br /&gt;
        thumb.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var target = bar.__clbiScrollTarget;&lt;br /&gt;
            var startY;&lt;br /&gt;
            var startScroll;&lt;br /&gt;
            var maxScroll;&lt;br /&gt;
            var maxThumbTop;&lt;br /&gt;
            var trackHeight;&lt;br /&gt;
            var thumbHeight;&lt;br /&gt;
&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            startY = e.clientY;&lt;br /&gt;
            startScroll = target.scrollTop;&lt;br /&gt;
            maxScroll = Math.max(1, target.scrollHeight - target.clientHeight);&lt;br /&gt;
            trackHeight = track ? track.clientHeight : 0;&lt;br /&gt;
            thumbHeight = thumb.offsetHeight || 0;&lt;br /&gt;
            maxThumbTop = Math.max(1, trackHeight - thumbHeight);&lt;br /&gt;
&lt;br /&gt;
            bar.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
            function onMove(moveEvent) {&lt;br /&gt;
                var dy = moveEvent.clientY - startY;&lt;br /&gt;
                target.scrollTop = startScroll + (dy / maxThumbTop) * maxScroll;&lt;br /&gt;
                updateClbiCustomScrollbar(bar);&lt;br /&gt;
                moveEvent.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function onUp() {&lt;br /&gt;
                bar.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                document.removeEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
                document.removeEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            document.addEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
            document.addEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!scrollEl.__clbiCustomScrollbarBound) {&lt;br /&gt;
        scrollEl.__clbiCustomScrollbarBound = true;&lt;br /&gt;
        scrollEl.addEventListener(&#039;scroll&#039;, function () {&lt;br /&gt;
            if (scrollEl.__clbiCustomScrollbar) {&lt;br /&gt;
                updateClbiCustomScrollbar(scrollEl.__clbiCustomScrollbar);&lt;br /&gt;
            }&lt;br /&gt;
        }, { passive: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scrollEl.__clbiCustomScrollbar = bar;&lt;br /&gt;
    updateClbiCustomScrollbar(bar);&lt;br /&gt;
&lt;br /&gt;
    return bar;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiCustomScrollbar(bar) {&lt;br /&gt;
    var scrollEl = bar &amp;amp;&amp;amp; bar.__clbiScrollTarget;&lt;br /&gt;
    var track = bar ? bar.querySelector(&#039;.clbi-custom-scroll-track&#039;) : null;&lt;br /&gt;
    var thumb = bar ? bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;) : null;&lt;br /&gt;
    var maxScroll;&lt;br /&gt;
    var trackHeight;&lt;br /&gt;
    var thumbHeight;&lt;br /&gt;
    var maxTop;&lt;br /&gt;
    var top;&lt;br /&gt;
&lt;br /&gt;
    if (!bar || !scrollEl || !track || !thumb) return;&lt;br /&gt;
&lt;br /&gt;
    maxScroll = scrollEl.scrollHeight - scrollEl.clientHeight;&lt;br /&gt;
    if (maxScroll &amp;lt;= 1) {&lt;br /&gt;
        bar.classList.add(&#039;is-hidden&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.classList.remove(&#039;is-hidden&#039;);&lt;br /&gt;
&lt;br /&gt;
    trackHeight = Math.max(1, track.clientHeight || 1);&lt;br /&gt;
    thumbHeight = Math.max(12, Math.floor((scrollEl.clientHeight / Math.max(scrollEl.scrollHeight, 1)) * trackHeight));&lt;br /&gt;
    thumbHeight = Math.min(trackHeight, thumbHeight);&lt;br /&gt;
    maxTop = Math.max(0, trackHeight - thumbHeight);&lt;br /&gt;
    top = maxScroll &amp;gt; 0 ? Math.round((scrollEl.scrollTop / maxScroll) * maxTop) : 0;&lt;br /&gt;
&lt;br /&gt;
    thumb.style.height = thumbHeight + &#039;px&#039;;&lt;br /&gt;
    thumb.style.transform = &#039;translateY(&#039; + top + &#039;px)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiCustomDocumentScrollbars() {&lt;br /&gt;
    var existing = Array.prototype.slice.call(document.querySelectorAll(&#039;.clbi-custom-scrollbar&#039;));&lt;br /&gt;
    var targets = getClbiDocumentScrollTargets();&lt;br /&gt;
    var liveBars = [];&lt;br /&gt;
&lt;br /&gt;
    if (!targets.length) {&lt;br /&gt;
        existing.forEach(function (bar) { bar.remove(); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    targets.forEach(function (scrollEl) {&lt;br /&gt;
        var well = getClbiOuterWellForScroll(scrollEl);&lt;br /&gt;
        var bar;&lt;br /&gt;
&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
        bar = buildClbiCustomScrollbar(well, scrollEl);&lt;br /&gt;
        liveBars.push(bar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    existing.forEach(function (bar) {&lt;br /&gt;
        if (liveBars.indexOf(bar) === -1) bar.remove();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (!window.__clbiCustomScrollbarResizeBound) {&lt;br /&gt;
    window.__clbiCustomScrollbarResizeBound = true;&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        setTimeout(initClbiCustomDocumentScrollbars, 60);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시간 계산 함수&lt;br /&gt;
function timeAgo(timestamp) {&lt;br /&gt;
    var now = new Date();&lt;br /&gt;
    var date = new Date(timestamp);&lt;br /&gt;
    var diff = Math.floor((now - date) / 1000);&lt;br /&gt;
&lt;br /&gt;
    if (diff &amp;lt; 60) return diff + &#039;초 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 3600) return Math.floor(diff / 60) + &#039;분 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 86400) return Math.floor(diff / 3600) + &#039;시간 전&#039;;&lt;br /&gt;
    return Math.floor(diff / 86400) + &#039;일 전&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
function getFoldTexts() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return (window.LANG &amp;amp;&amp;amp; window.LANG[lang])&lt;br /&gt;
        ? window.LANG[lang]&lt;br /&gt;
        : (window.LANG ? window.LANG.ko : { expand: &#039;펼치기&#039;, collapse: &#039;접기&#039; });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function refreshOpenAncestors($start) {&lt;br /&gt;
    $start.parents(&#039;[id^=&amp;quot;collapsible&amp;quot;]&#039;).each(function () {&lt;br /&gt;
        var $parent = $(this);&lt;br /&gt;
        if (!$parent.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        // 이미 fully open 상태면 굳이 다시 잠그지 않음&lt;br /&gt;
        if ($parent.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $parent.css(&#039;max-height&#039;, this.scrollHeight + &#039;px&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindInnerResizeUpdates($target) {&lt;br /&gt;
    // 이미지 늦게 로드될 때 높이 갱신&lt;br /&gt;
    $target.find(&#039;img&#039;).off(&#039;.foldimg&#039;).on(&#039;load.foldimg&#039;, function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            if ($target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
                $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function openFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;opening&#039;);&lt;br /&gt;
    $target.addClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 열린 뒤 자연 확장 가능하게 만들기 위해 먼저 px로 열기&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.collapse);&lt;br /&gt;
&lt;br /&gt;
    bindInnerResizeUpdates($target);&lt;br /&gt;
&lt;br /&gt;
    // 바깥 펼접 즉시 갱신&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    // 전환 끝나면 none으로 풀어서 중첩 펼접/동적 내용 증가를 자연스럽게 허용&lt;br /&gt;
    $target.off(&#039;transitionend.foldopen&#039;).on(&#039;transitionend.foldopen&#039;, function (e) {&lt;br /&gt;
        if (e.target !== this) return;&lt;br /&gt;
        if (!$target.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        $target.css(&#039;max-height&#039;, &#039;none&#039;);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;open&#039;);&lt;br /&gt;
&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // 늦게 렌더되는 콘텐츠 대응&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 80);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 220);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function closeFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    // none 상태에서 닫으면 transition이 안 되므로 실제 높이로 고정&lt;br /&gt;
    if ($target.css(&#039;max-height&#039;) === &#039;none&#039; || $target.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;closing&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target.removeClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.expand);&lt;br /&gt;
&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;closed&#039;);&lt;br /&gt;
    }, 250);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function () {&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiToggle&#039;, &#039;.toggleBtn&#039;, function () {&lt;br /&gt;
            var $btn = $(this);&lt;br /&gt;
            var targetId = $btn.data(&#039;target&#039;);&lt;br /&gt;
            var $target = $(&#039;#&#039; + targetId);&lt;br /&gt;
            if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
            var scrollY = window.scrollY;&lt;br /&gt;
&lt;br /&gt;
            if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
                closeFold($target, $btn);&lt;br /&gt;
            } else {&lt;br /&gt;
                openFold($target, $btn);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            window.scrollTo(0, scrollY);&lt;br /&gt;
        });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// ========== 프로필 시스템 ==========&lt;br /&gt;
function initProfile() {&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var token = ++PROFILE_RENDER_TOKEN;&lt;br /&gt;
    var ns = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var title = mw.config.get(&#039;wgTitle&#039;);&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    var isProfileSettings = specialPage === &#039;사용자정보&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, ns === 2);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-settings-page&#039;, isProfileSettings);&lt;br /&gt;
&lt;br /&gt;
    if (ns === 2) {&lt;br /&gt;
        var profileUser = title.split(&#039;/&#039;)[0];&lt;br /&gt;
        renderProfile(profileUser, token);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isProfileSettings) {&lt;br /&gt;
        initUserProfilePage();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderProfile(username, token) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        list: &#039;users&#039;,&lt;br /&gt;
        ususers: username,&lt;br /&gt;
        usprop: &#039;editcount&#039;&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        if (token !== PROFILE_RENDER_TOKEN) return;&lt;br /&gt;
        if (mw.config.get(&#039;wgNamespaceNumber&#039;) !== 2) return;&lt;br /&gt;
&lt;br /&gt;
        var currentTitle = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).split(&#039;/&#039;)[0];&lt;br /&gt;
        if (currentTitle !== username) return;&lt;br /&gt;
&lt;br /&gt;
        var user = data.query.users[0];&lt;br /&gt;
        var contentEl = document.getElementById(&#039;mw-content-text&#039;);&lt;br /&gt;
        if (!contentEl) return;&lt;br /&gt;
&lt;br /&gt;
        var pageContent = contentEl.querySelector(&#039;.mw-parser-output&#039;) || contentEl;&lt;br /&gt;
        injectProfileCard(username, user, pageContent);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function injectProfileCard(username, userData, container) {&lt;br /&gt;
    var isOwnPage = mw.config.get(&#039;wgUserName&#039;) === username;&lt;br /&gt;
    var editCount = (userData &amp;amp;&amp;amp; userData.editcount) ? userData.editcount : 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.classList.add(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var safeUsername = escapeHtml(username);&lt;br /&gt;
    var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(username) + &#039;.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var fallbackSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var editBtn = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;a href=&amp;quot;/index.php/특수:사용자정보&amp;quot; class=&amp;quot;profile-edit-btn&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-label&amp;quot;&amp;gt;프로필 수정&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-arrow&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var progressHtml = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;div class=&amp;quot;profile-page-progress is-syncing&amp;quot; data-profile-progress&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;LEVEL RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-page-progress-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-level&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;profile-page-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-sub&amp;quot;&amp;gt;SYNCING&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-meta&amp;quot;&amp;gt;TODAY — · DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var card = document.createElement(&#039;div&#039;);&lt;br /&gt;
    card.className = &#039;profile-card profile-page-console&#039;;&lt;br /&gt;
    card.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-titlebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;USER PROFILE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;OFFICIAL ARCHIVE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-identity-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-avatar-bay&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;&#039; + fallbackSrc + &#039;\&#039;;&amp;quot; alt=&amp;quot;&#039; + safeUsername + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-info-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-nameplate&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;h2 class=&amp;quot;profile-username&amp;quot;&amp;gt;&#039; + safeUsername + &#039;&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
                        editBtn +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-name&amp;quot; data-field=&amp;quot;name&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-role&amp;quot; data-field=&amp;quot;role&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-discord&amp;quot; data-field=&amp;quot;discord&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-lower-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-bio-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;BIOGRAPHY&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-bio&amp;quot; data-field=&amp;quot;bio&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-stats-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-stats&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        progressHtml +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-stat-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot;&amp;gt;&#039; + editCount + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;수정 횟수&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot; data-contrib-pages-stat&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot; data-contrib-pages-value&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;기여 문서&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-info-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;TIME&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-time&amp;gt;UTC --:--&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;LANGUAGE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-language&amp;gt;—&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    container.insertBefore(card, container.firstChild);&lt;br /&gt;
    loadProfileFields(username, card);&lt;br /&gt;
    loadProfileContributionPages(username, card);&lt;br /&gt;
    updateProfilePageEnvironment(card, null);&lt;br /&gt;
&lt;br /&gt;
    if (isOwnPage) {&lt;br /&gt;
        loadProfileProgressForUserPage(card);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getProfileLanguageLabel() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || (lang ? lang.toUpperCase() : &#039;—&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfilePageEnvironment(card, summary) {&lt;br /&gt;
    if (!card) return;&lt;br /&gt;
&lt;br /&gt;
    var timezone = summary &amp;amp;&amp;amp; summary.timezone ? summary.timezone : &#039;UTC&#039;;&lt;br /&gt;
    var timeEl = card.querySelector(&#039;[data-profile-time]&#039;);&lt;br /&gt;
    var langEl = card.querySelector(&#039;[data-profile-language]&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (timeEl) {&lt;br /&gt;
        try {&lt;br /&gt;
            timeEl.textContent = timezone + &#039; &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: timezone&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            timeEl.textContent = &#039;UTC &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: &#039;UTC&#039;&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (langEl) {&lt;br /&gt;
        langEl.textContent = getProfileLanguageLabel();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileContributionPages(username, card) {&lt;br /&gt;
    if (!username || !card) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var valueEl = card.querySelector(&#039;[data-contrib-pages-value]&#039;);&lt;br /&gt;
    if (valueEl) valueEl.textContent = &#039;SYNC&#039;;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        var pages = Object.create(null);&lt;br /&gt;
        var cont = {};&lt;br /&gt;
        var guard = 0;&lt;br /&gt;
&lt;br /&gt;
        function requestNext() {&lt;br /&gt;
            guard++;&lt;br /&gt;
&lt;br /&gt;
            var params = Object.assign({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                list: &#039;usercontribs&#039;,&lt;br /&gt;
                ucuser: username,&lt;br /&gt;
                ucnamespace: 0,&lt;br /&gt;
                ucprop: &#039;title&#039;,&lt;br /&gt;
                uclimit: &#039;max&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }, cont);&lt;br /&gt;
&lt;br /&gt;
            return api.get(params).then(function (data) {&lt;br /&gt;
                var rows = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.usercontribs ? data.query.usercontribs : [];&lt;br /&gt;
&lt;br /&gt;
                rows.forEach(function (row) {&lt;br /&gt;
                    if (row &amp;amp;&amp;amp; row.title) pages[row.title] = true;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                if (data &amp;amp;&amp;amp; data.continue &amp;amp;&amp;amp; data.continue.uccontinue &amp;amp;&amp;amp; guard &amp;lt; 40) {&lt;br /&gt;
                    cont = data.continue;&lt;br /&gt;
                    return requestNext();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (valueEl) valueEl.textContent = Object.keys(pages).length;&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestNext().fail(function () {&lt;br /&gt;
            if (valueEl) valueEl.textContent = &#039;—&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileProgressForUserPage(card) {&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;progress_summary&#039;,&lt;br /&gt;
            format: &#039;json&#039;,&lt;br /&gt;
            formatversion: 2&lt;br /&gt;
        }).then(function (data) {&lt;br /&gt;
            var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
            if (!payload || !payload.available || !payload.summary) return;&lt;br /&gt;
            updateUserPageProgress(card, payload.summary);&lt;br /&gt;
            updateProfilePageEnvironment(card, payload.summary);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateUserPageProgress(card, summary) {&lt;br /&gt;
    var panel = card.querySelector(&#039;[data-profile-progress]&#039;);&lt;br /&gt;
    if (!panel || !summary) return;&lt;br /&gt;
&lt;br /&gt;
    var level = summary.level || 1;&lt;br /&gt;
    var totalXp = summary.totalXp || 0;&lt;br /&gt;
    var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
    var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
    var percent = Math.max(0, Math.min(100, summary.progressPercent || 0));&lt;br /&gt;
    var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
    var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
    var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
&lt;br /&gt;
    panel.classList.remove(&#039;is-syncing&#039;);&lt;br /&gt;
    panel.classList.toggle(&#039;is-max-level&#039;, isMaxLevel);&lt;br /&gt;
&lt;br /&gt;
    var levelEl = panel.querySelector(&#039;.profile-page-level&#039;);&lt;br /&gt;
    var totalEl = panel.querySelector(&#039;.profile-page-total-xp&#039;);&lt;br /&gt;
    var fillEl = panel.querySelector(&#039;.profile-page-xp-fill&#039;);&lt;br /&gt;
    var subEl = panel.querySelector(&#039;.profile-page-progress-sub&#039;);&lt;br /&gt;
    var metaEl = panel.querySelector(&#039;.profile-page-progress-meta&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (levelEl) levelEl.textContent = (isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level;&lt;br /&gt;
    if (totalEl) totalEl.textContent = totalXp + &#039; XP&#039;;&lt;br /&gt;
    if (fillEl) fillEl.style.width = percent + &#039;%&#039;;&lt;br /&gt;
    if (subEl) subEl.textContent = isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;);&lt;br /&gt;
    if (metaEl) metaEl.textContent = &#039;TODAY &#039; + dailyXp + &#039; XP · DISCOVERED &#039; + discoveries;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileFields(username, card) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;userprofile&#039;,&lt;br /&gt;
        user: username&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var profile = data.userprofile;&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: profile.name || &#039;&#039;,&lt;br /&gt;
            discord: profile.discord || &#039;&#039;,&lt;br /&gt;
            role: profile.role || &#039;&#039;,&lt;br /&gt;
            bio: profile.bio || &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    }).fail(function() {&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: &#039;&#039;,&lt;br /&gt;
            discord: &#039;&#039;,&lt;br /&gt;
            role: &#039;&#039;,&lt;br /&gt;
            bio: &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfileFields(card, data) {&lt;br /&gt;
    var nameEl = card.querySelector(&#039;[data-field=&amp;quot;name&amp;quot;]&#039;);&lt;br /&gt;
    var roleEl = card.querySelector(&#039;[data-field=&amp;quot;role&amp;quot;]&#039;);&lt;br /&gt;
    var discordEl = card.querySelector(&#039;[data-field=&amp;quot;discord&amp;quot;]&#039;);&lt;br /&gt;
    var bioEl = card.querySelector(&#039;[data-field=&amp;quot;bio&amp;quot;]&#039;);&lt;br /&gt;
    if (nameEl) nameEl.textContent = data.name || &#039;&#039;;&lt;br /&gt;
    if (roleEl) roleEl.textContent = data.role || &#039;&#039;;&lt;br /&gt;
    if (discordEl) discordEl.textContent = data.discord ? (&#039;디스코드: &#039; + data.discord) : &#039;&#039;;&lt;br /&gt;
    if (bioEl) bioEl.textContent = data.bio || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
// ========== 프로필 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
// ========== 알림 시스템 ==========&lt;br /&gt;
function ensureNotificationPopup() {&lt;br /&gt;
    if (document.getElementById(&#039;clbi-notification-popup&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    var popup = document.createElement(&#039;div&#039;);&lt;br /&gt;
    popup.id = &#039;clbi-notification-popup&#039;;&lt;br /&gt;
    popup.style.cssText =&lt;br /&gt;
        &#039;display:none;position:fixed;z-index:99999;width:320px;max-height:420px;&#039; +&lt;br /&gt;
        &#039;background:#0a0909;border:2px solid #854369;border-radius:5px;&#039; +&lt;br /&gt;
        &#039;box-shadow:0 0 0 1px #1a1a1a, 0 8px 24px rgba(0,0,0,0.55);overflow:hidden;&#039;;&lt;br /&gt;
&lt;br /&gt;
    popup.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:10px 12px;border-bottom:2px solid #854369;background:linear-gradient(to bottom, #171114 0%, #0a0909 100%);color:#E2E2E2;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:space-between;gap:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;알림&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-notification-readall&amp;quot; style=&amp;quot;background:#171717;border:1px solid #854369;border-radius:6px;color:#E2E2E2;font-size:11px;font-weight:700;padding:4px 8px;cursor:pointer;&amp;quot;&amp;gt;전체 읽음&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;clbi-notification-list&amp;quot; style=&amp;quot;max-height:320px;overflow-y:auto;padding:8px 0;color:#E2E2E2;font-size:12px;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:8px;border-top:1px solid #2a2a2a;background:#111;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;/index.php?title=Special:Notifications&amp;quot; id=&amp;quot;clbi-notification-more&amp;quot; style=&amp;quot;display:block;width:100%;text-align:center;padding:8px 10px;border-radius:6px;background:#171717;border:1px solid #854369;color:#E2E2E2 !important;text-decoration:none !important;font-size:12px;font-weight:700;&amp;quot;&amp;gt;더보기&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    document.body.appendChild(popup);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function positionNotificationPopup() {&lt;br /&gt;
    var btn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
    var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
    if (!btn || !popup) return;&lt;br /&gt;
&lt;br /&gt;
    var rect = btn.getBoundingClientRect();&lt;br /&gt;
    var top = rect.bottom + 6;&lt;br /&gt;
    var left = rect.left + (rect.width / 2) - (popup.offsetWidth / 2);&lt;br /&gt;
&lt;br /&gt;
    if (left &amp;lt; 8) left = 8;&lt;br /&gt;
    if (left + popup.offsetWidth &amp;gt; window.innerWidth - 8) {&lt;br /&gt;
        left = window.innerWidth - popup.offsetWidth - 8;&lt;br /&gt;
    }&lt;br /&gt;
    if (top + popup.offsetHeight &amp;gt; window.innerHeight - 8) {&lt;br /&gt;
        top = Math.max(8, rect.top - popup.offsetHeight - 6);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    popup.style.top = top + &#039;px&#039;;&lt;br /&gt;
    popup.style.left = left + &#039;px&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseNotificationItemsFromHtml(html) {&lt;br /&gt;
    var parser = new DOMParser();&lt;br /&gt;
    var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.mw-echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;.mw-echo-ui-notificationsInboxWidgetRow&#039;,&lt;br /&gt;
        &#039;.echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;li[data-notification-id]&#039;,&lt;br /&gt;
        &#039;.mw-echo-notifications-list li&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var items = [];&lt;br /&gt;
    for (var i = 0; i &amp;lt; selectors.length; i++) {&lt;br /&gt;
        items = Array.prototype.slice.call(doc.querySelectorAll(selectors[i]));&lt;br /&gt;
        if (items.length) break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return items.slice(0, 5).map(function(item) {&lt;br /&gt;
        var link = item.querySelector(&#039;a[href]&#039;);&lt;br /&gt;
        var href = link ? link.getAttribute(&#039;href&#039;) : &#039;/index.php?title=Special:Notifications&#039;;&lt;br /&gt;
        var text = (item.textContent || &#039;&#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        var notificationId =&lt;br /&gt;
            item.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
            &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!notificationId) {&lt;br /&gt;
            var anyWithId = item.querySelector(&#039;[data-notification-id], [data-id], [data-notification]&#039;);&lt;br /&gt;
            if (anyWithId) {&lt;br /&gt;
                notificationId =&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (href &amp;amp;&amp;amp; href.indexOf(&#039;http&#039;) !== 0) {&lt;br /&gt;
            href = href.charAt(0) === &#039;/&#039;&lt;br /&gt;
                ? href&lt;br /&gt;
                : &#039;/index.php&#039; + (href.charAt(0) === &#039;?&#039; ? href : &#039;/&#039; + href);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            id: notificationId,&lt;br /&gt;
            href: href,&lt;br /&gt;
            text: text || &#039;알림&#039;&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setNotificationIcon(hasItems) {&lt;br /&gt;
    var quickIcon = document.getElementById(&#039;profile-quick-notification-icon&#039;);&lt;br /&gt;
    var svg = hasItems ? CLBI_SVG_BELL_DOT : CLBI_SVG_BELL;&lt;br /&gt;
&lt;br /&gt;
    if (quickIcon) {&lt;br /&gt;
        quickIcon.innerHTML = svg;&lt;br /&gt;
        quickIcon.classList.toggle(&#039;has-notifications&#039;, !!hasItems);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderNotificationPopup(items) {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    var badge = document.getElementById(&#039;clbi-notification-badge&#039;);&lt;br /&gt;
    if (!list) return;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;표시할 알림이 없습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        if (badge) badge.style.display = &#039;none&#039;;&lt;br /&gt;
        setNotificationIcon(false);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
    for (var i = 0; i &amp;lt; items.length; i++) {&lt;br /&gt;
        html +=&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;&#039; + items[i].href + &#039;&amp;quot; class=&amp;quot;clbi-notification-item&amp;quot; data-notification-id=&amp;quot;&#039; + (items[i].id || &#039;&#039;) + &#039;&amp;quot; style=&amp;quot;display:block;padding:10px 12px;color:#E2E2E2 !important;text-decoration:none !important;border-bottom:1px solid #1f1f1f;line-height:1.5;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                items[i].text +&lt;br /&gt;
            &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    list.innerHTML = html;&lt;br /&gt;
&lt;br /&gt;
    if (badge) {&lt;br /&gt;
        badge.textContent = items.length;&lt;br /&gt;
        badge.style.display = &#039;block&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    setNotificationIcon(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadNotificationsIntoPopup() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetch(&#039;/index.php?title=Special:Notifications&#039;, { credentials: &#039;same-origin&#039; })&lt;br /&gt;
        .then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        })&lt;br /&gt;
        .then(function(html) {&lt;br /&gt;
            var items = parseNotificationItemsFromHtml(html);&lt;br /&gt;
            renderNotificationPopup(items);&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function(err) {&lt;br /&gt;
            console.error(err);&lt;br /&gt;
            if (list) {&lt;br /&gt;
                list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;알림을 불러오지 못했습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markAllNotificationsRead() {&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: &#039;all&#039;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markNotificationReadById(notificationId) {&lt;br /&gt;
    if (!notificationId) {&lt;br /&gt;
        return $.Deferred().resolve().promise();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: notificationId&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initNotifications() {&lt;br /&gt;
    var quickBtn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!quickBtn) return;&lt;br /&gt;
&lt;br /&gt;
    ensureNotificationPopup();&lt;br /&gt;
    loadNotificationsIntoPopup();&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationToggle&#039;, &#039;#profile-quick-notifications&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (popup.style.display === &#039;none&#039; || popup.style.display === &#039;&#039;) {&lt;br /&gt;
                popup.style.display = &#039;block&#039;;&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
            } else {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationOutside&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationOutside&#039;, function(e) {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            var quickToggle = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (!popup.contains(e.target) &amp;amp;&amp;amp; (!quickToggle || !quickToggle.contains(e.target))) {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationReadAll&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationReadAll&#039;, &#039;#clbi-notification-readall&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var button = this;&lt;br /&gt;
            button.disabled = true;&lt;br /&gt;
            button.textContent = &#039;처리 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            markAllNotificationsRead()&lt;br /&gt;
                .then(function() {&lt;br /&gt;
                    loadNotificationsIntoPopup();&lt;br /&gt;
                })&lt;br /&gt;
                .always(function() {&lt;br /&gt;
                    button.disabled = false;&lt;br /&gt;
                    button.textContent = &#039;전체 읽음&#039;;&lt;br /&gt;
                });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationItem&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationItem&#039;, &#039;.clbi-notification-item&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var href = this.getAttribute(&#039;href&#039;);&lt;br /&gt;
            var notificationId = this.getAttribute(&#039;data-notification-id&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            markNotificationReadById(notificationId).always(function() {&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
                if (href) {&lt;br /&gt;
                    window.location.href = href;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(window)&lt;br /&gt;
        .off(&#039;resize.clbiNotification&#039;)&lt;br /&gt;
        .on(&#039;resize.clbiNotification&#039;, function() {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (popup &amp;amp;&amp;amp; popup.style.display === &#039;block&#039;) {&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
// ========== 알림 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
function initUserProfilePage() {&lt;br /&gt;
    $(&#039;body&#039;).addClass(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
&lt;br /&gt;
    var saveBtn = document.getElementById(&#039;pref-save&#039;);&lt;br /&gt;
    if (!saveBtn) return;&lt;br /&gt;
&lt;br /&gt;
    function getPrefRow(id) {&lt;br /&gt;
        var el = document.getElementById(id);&lt;br /&gt;
        if (!el) return null;&lt;br /&gt;
        return el.closest(&#039;.clbi-pref-row&#039;) || el.parentNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removePrefRow(id) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (row &amp;amp;&amp;amp; row.parentNode) {&lt;br /&gt;
            row.parentNode.removeChild(row);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPrefSection(className, titleText) {&lt;br /&gt;
        var section = document.createElement(&#039;div&#039;);&lt;br /&gt;
        section.className = &#039;clbi-pref-section &#039; + className;&lt;br /&gt;
&lt;br /&gt;
        var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;clbi-pref-section-title&#039;;&lt;br /&gt;
        title.textContent = titleText;&lt;br /&gt;
&lt;br /&gt;
        var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
        body.className = &#039;clbi-pref-section-body&#039;;&lt;br /&gt;
&lt;br /&gt;
        section.appendChild(title);&lt;br /&gt;
        section.appendChild(body);&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            section: section,&lt;br /&gt;
            body: body&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveRowToSection(id, targetBody, className) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (!row || !targetBody) return false;&lt;br /&gt;
&lt;br /&gt;
        row.classList.add(&#039;clbi-pref-row-key-&#039; + className);&lt;br /&gt;
        targetBody.appendChild(row);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebuildProfileSettingsLayout() {&lt;br /&gt;
        var root = document.querySelector(&#039;.clbi-prefs-profile&#039;);&lt;br /&gt;
        if (!root || root.dataset.profileSettingsReworked === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        root.dataset.profileSettingsReworked = &#039;1&#039;;&lt;br /&gt;
        root.classList.add(&#039;profile-settings-console&#039;);&lt;br /&gt;
&lt;br /&gt;
        removePrefRow(&#039;pref-badges&#039;);&lt;br /&gt;
&lt;br /&gt;
        var originalRows = Array.prototype.slice.call(root.querySelectorAll(&#039;.clbi-pref-row&#039;));&lt;br /&gt;
        var actionNodes = [];&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn.parentNode === root || saveBtn.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(saveBtn);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var statusNode = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        if (statusNode &amp;amp;&amp;amp; statusNode.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(statusNode);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var main = document.createElement(&#039;div&#039;);&lt;br /&gt;
        main.className = &#039;clbi-pref-main-grid&#039;;&lt;br /&gt;
&lt;br /&gt;
        var media = createPrefSection(&#039;clbi-pref-section-media&#039;, &#039;PROFILE IMAGE&#039;);&lt;br /&gt;
        var identity = createPrefSection(&#039;clbi-pref-section-identity&#039;, &#039;IDENTITY RECORD&#039;);&lt;br /&gt;
        var bio = createPrefSection(&#039;clbi-pref-section-bio&#039;, &#039;BIOGRAPHY&#039;);&lt;br /&gt;
        var account = createPrefSection(&#039;clbi-pref-section-account&#039;, &#039;ACCOUNT CONTACT&#039;);&lt;br /&gt;
        var misc = createPrefSection(&#039;clbi-pref-section-misc&#039;, &#039;OTHER OPTIONS&#039;);&lt;br /&gt;
&lt;br /&gt;
        main.appendChild(media.section);&lt;br /&gt;
        main.appendChild(identity.section);&lt;br /&gt;
        main.appendChild(bio.section);&lt;br /&gt;
        main.appendChild(account.section);&lt;br /&gt;
        main.appendChild(misc.section);&lt;br /&gt;
&lt;br /&gt;
        root.innerHTML = &#039;&#039;;&lt;br /&gt;
        root.appendChild(main);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-preview&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-btn&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-input&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-name&#039;, identity.body, &#039;name&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-role&#039;, identity.body, &#039;role&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-discord&#039;, identity.body, &#039;discord&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-bio&#039;, bio.body, &#039;bio&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-new-email&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-password&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-save&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
&lt;br /&gt;
        originalRows.forEach(function (row) {&lt;br /&gt;
            if (!row.parentNode &amp;amp;&amp;amp; !row.className.match(/clbi-pref-row-key-/)) {&lt;br /&gt;
                misc.body.appendChild(row);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!misc.body.children.length) {&lt;br /&gt;
            misc.section.parentNode.removeChild(misc.section);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var actions = document.createElement(&#039;div&#039;);&lt;br /&gt;
        actions.className = &#039;clbi-pref-actions&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn) actions.appendChild(saveBtn);&lt;br /&gt;
        if (statusNode) actions.appendChild(statusNode);&lt;br /&gt;
&lt;br /&gt;
        root.appendChild(actions);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    rebuildProfileSettingsLayout();&lt;br /&gt;
&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    var selectedFile = null;&lt;br /&gt;
    var cropper = null;&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-gallery-modal&#039;)) {&lt;br /&gt;
        var gModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        gModal.id = &#039;clbi-gallery-modal&#039;;&lt;br /&gt;
        gModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        gModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:480px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;justify-content:space-between;align-items:center;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;프로필 사진 선택&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-close&amp;quot; style=&amp;quot;background:none;border:none;color:#aaa;font-size:18px;cursor:pointer;&amp;quot;&amp;gt;✕&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-upload-btn&amp;quot; style=&amp;quot;background:#2a2a2a;border:2px dashed #854369;border-radius:8px;padding:32px;color:#e2e2e2;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:8px;font-size:13px;width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:32px;&amp;quot;&amp;gt;🖼️&amp;lt;/span&amp;gt;새 사진 업로드&#039; +&lt;br /&gt;
                &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history-section&amp;quot; style=&amp;quot;display:none;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div style=&amp;quot;font-size:11px;color:#888;margin-bottom:8px;&amp;quot;&amp;gt;이전 사진 — 클릭하면 바로 적용&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history&amp;quot; style=&amp;quot;display:flex;gap:8px;flex-wrap:wrap;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(gModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-crop-modal&#039;)) {&lt;br /&gt;
        var cModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        cModal.id = &#039;clbi-crop-modal&#039;;&lt;br /&gt;
        cModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        cModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:500px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;사진 조정&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;width:100%;max-height:380px;overflow:hidden;border-radius:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img id=&amp;quot;clbi-crop-image&amp;quot; style=&amp;quot;max-width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;gap:8px;justify-content:flex-end;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-cancel&amp;quot; style=&amp;quot;background:#2a2a2a;color:#e2e2e2;border:1px solid #444;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;취소&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-confirm&amp;quot; style=&amp;quot;background:#854369;color:#fff;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;확정&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(cModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gModal = document.getElementById(&#039;clbi-gallery-modal&#039;);&lt;br /&gt;
    var cModal = document.getElementById(&#039;clbi-crop-modal&#039;);&lt;br /&gt;
    var cropImage = document.getElementById(&#039;clbi-crop-image&#039;);&lt;br /&gt;
    var pfpInput = document.getElementById(&#039;pref-pfp-input&#039;);&lt;br /&gt;
&lt;br /&gt;
    function openGallery() {&lt;br /&gt;
        gModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;query&#039;,&lt;br /&gt;
            titles: &#039;파일:Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
            prop: &#039;imageinfo&#039;,&lt;br /&gt;
            iiprop: &#039;url|timestamp&#039;,&lt;br /&gt;
            iilimit: 6&lt;br /&gt;
        }).then(function(data) {&lt;br /&gt;
            var pages = data.query.pages;&lt;br /&gt;
            var page = pages[Object.keys(pages)[0]];&lt;br /&gt;
            if (!page.imageinfo || page.imageinfo.length === 0) return;&lt;br /&gt;
&lt;br /&gt;
            var historyEl = document.getElementById(&#039;clbi-gallery-history&#039;);&lt;br /&gt;
            var sectionEl = document.getElementById(&#039;clbi-gallery-history-section&#039;);&lt;br /&gt;
            historyEl.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            page.imageinfo.forEach(function(info, idx) {&lt;br /&gt;
                var wrap = document.createElement(&#039;div&#039;);&lt;br /&gt;
                wrap.style.cssText = &#039;position:relative;cursor:pointer;&#039;;&lt;br /&gt;
&lt;br /&gt;
                var img = document.createElement(&#039;img&#039;);&lt;br /&gt;
                img.src = info.url;&lt;br /&gt;
                img.style.cssText =&lt;br /&gt;
                    &#039;width:72px;height:72px;object-fit:cover;border-radius:8px;border:2px solid #444;flex-shrink:0;&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (idx === 0) {&lt;br /&gt;
                    img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                    var badge = document.createElement(&#039;div&#039;);&lt;br /&gt;
                    badge.textContent = &#039;현재&#039;;&lt;br /&gt;
                    badge.style.cssText =&lt;br /&gt;
                        &#039;position:absolute;bottom:4px;left:50%;transform:translateX(-50%);background:#854369;color:#fff;font-size:9px;padding:1px 6px;border-radius:10px;&#039;;&lt;br /&gt;
                    wrap.appendChild(badge);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseenter&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseleave&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#444&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
                    fetch(info.url)&lt;br /&gt;
                        .then(function(r) {&lt;br /&gt;
                            return r.blob();&lt;br /&gt;
                        })&lt;br /&gt;
                        .then(function(blob) {&lt;br /&gt;
                            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
                            gModal.style.display = &#039;none&#039;;&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                wrap.appendChild(img);&lt;br /&gt;
                historyEl.appendChild(wrap);&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            sectionEl.style.display = &#039;block&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function openCrop(src) {&lt;br /&gt;
        cropImage.src = src;&lt;br /&gt;
        cModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            cropper = new Cropper(cropImage, {&lt;br /&gt;
                aspectRatio: 1,&lt;br /&gt;
                viewMode: 1,&lt;br /&gt;
                dragMode: &#039;move&#039;,&lt;br /&gt;
                autoCropArea: 0.8,&lt;br /&gt;
                cropBoxResizable: true,&lt;br /&gt;
                cropBoxMovable: true&lt;br /&gt;
            });&lt;br /&gt;
        }, 150);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;pref-pfp-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        openGallery();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-upload-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        pfpInput.click();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-close&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    pfpInput.addEventListener(&#039;change&#039;, function() {&lt;br /&gt;
        var file = this.files[0];&lt;br /&gt;
        if (!file) return;&lt;br /&gt;
&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
&lt;br /&gt;
        var reader = new FileReader();&lt;br /&gt;
        reader.onload = function(e) {&lt;br /&gt;
            openCrop(e.target.result);&lt;br /&gt;
        };&lt;br /&gt;
        reader.readAsDataURL(file);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-cancel&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        cModal.style.display = &#039;none&#039;;&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
        pfpInput.value = &#039;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-confirm&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        if (!cropper) return;&lt;br /&gt;
&lt;br /&gt;
        var canvas = cropper.getCroppedCanvas({ width: 256, height: 256 });&lt;br /&gt;
        if (!canvas) return;&lt;br /&gt;
&lt;br /&gt;
        canvas.toBlob(function(blob) {&lt;br /&gt;
            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
            cModal.style.display = &#039;none&#039;;&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
        }, &#039;image/png&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var emailSaveBtn = document.getElementById(&#039;pref-email-save&#039;);&lt;br /&gt;
    if (emailSaveBtn) {&lt;br /&gt;
        emailSaveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
            var statusEl = document.getElementById(&#039;pref-email-status&#039;);&lt;br /&gt;
            var newEmail = document.getElementById(&#039;pref-new-email&#039;).value;&lt;br /&gt;
            var password = document.getElementById(&#039;pref-email-password&#039;).value;&lt;br /&gt;
&lt;br /&gt;
            if (!newEmail || !password) {&lt;br /&gt;
                statusEl.textContent = &#039;이메일과 비밀번호를 입력해주세요.&#039;;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            statusEl.textContent = &#039;변경 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;changeemail&#039;,&lt;br /&gt;
                email: newEmail,&lt;br /&gt;
                password: password&lt;br /&gt;
            }).then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 이메일 변경됨&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-new-email&#039;).value = &#039;&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-email-password&#039;).value = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 3000);&lt;br /&gt;
            }).fail(function(code, data) {&lt;br /&gt;
                var msg = data &amp;amp;&amp;amp; data.error &amp;amp;&amp;amp; data.error.info ? data.error.info : &#039;변경 실패&#039;;&lt;br /&gt;
                statusEl.textContent = msg;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    saveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        var statusEl = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        statusEl.textContent = &#039;저장 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
        var promises = [];&lt;br /&gt;
&lt;br /&gt;
        if (selectedFile) {&lt;br /&gt;
            var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;upload&#039;,&lt;br /&gt;
                    filename: &#039;Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
                    ignorewarnings: true,&lt;br /&gt;
                    file: selectedFile,&lt;br /&gt;
                    format: &#039;json&#039;&lt;br /&gt;
                }, {&lt;br /&gt;
                    contentType: &#039;multipart/form-data&#039;&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var fields = [&#039;name&#039;, &#039;discord&#039;, &#039;role&#039;, &#039;bio&#039;];&lt;br /&gt;
&lt;br /&gt;
        for (var i = 0; i &amp;lt; fields.length; i++) {&lt;br /&gt;
            var el = document.getElementById(&#039;pref-&#039; + fields[i]);&lt;br /&gt;
            if (!el) continue;&lt;br /&gt;
&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;options&#039;,&lt;br /&gt;
                    optionname: &#039;profile-&#039; + fields[i],&lt;br /&gt;
                    optionvalue: el.value&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $.when.apply($, promises)&lt;br /&gt;
            .then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 저장됨&#039;;&lt;br /&gt;
                selectedFile = null;&lt;br /&gt;
                document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;사진 선택&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 2000);&lt;br /&gt;
            })&lt;br /&gt;
            .fail(function() {&lt;br /&gt;
                statusEl.textContent = &#039;저장 실패&#039;;&lt;br /&gt;
            });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Banner / CRT Page Monitor thumbnail slices&lt;br /&gt;
   - base 이미지는 틀에 들어간 파일 문법 그대로 사용&lt;br /&gt;
   - slice 레이어에는 300px MediaWiki 썸네일만 삽입&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function ($, mw) {&lt;br /&gt;
    var thumbCache = {};&lt;br /&gt;
&lt;br /&gt;
    function parseSliceWidth(value) {&lt;br /&gt;
        var parsed = parseInt(value, 10);&lt;br /&gt;
&lt;br /&gt;
        if (!isFinite(parsed) || parsed &amp;lt; 120) {&lt;br /&gt;
            return 300;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parsed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getImageSrc(img) {&lt;br /&gt;
        return img ? (img.currentSrc || img.getAttribute(&#039;src&#039;) || img.src || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFileNameFromSrc(src) {&lt;br /&gt;
        var a;&lt;br /&gt;
        var parts;&lt;br /&gt;
        var fileName;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = src;&lt;br /&gt;
&lt;br /&gt;
        parts = (a.pathname || &#039;&#039;).split(&#039;/&#039;).filter(function (part) {&lt;br /&gt;
            return !!part;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!parts.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        fileName = parts.pop();&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * MediaWiki thumb URL 예시:&lt;br /&gt;
         * /images/thumb/a/ab/File.png/1000px-File.png&lt;br /&gt;
         * /images/thumb/a/ab/File.svg/1000px-File.svg.png&lt;br /&gt;
         *&lt;br /&gt;
         * 이 경우 실제 파일명은 마지막 조각이 아니라 그 앞 조각이다.&lt;br /&gt;
         */&lt;br /&gt;
        if (/^\d+px-/.test(fileName) &amp;amp;&amp;amp; parts.length) {&lt;br /&gt;
            fileName = parts.pop();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fileName = fileName.replace(/^\d+px-/, &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            fileName = decodeURIComponent(fileName);&lt;br /&gt;
        } catch (e) {}&lt;br /&gt;
&lt;br /&gt;
        return fileName;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveThumbUrl(img, width, callback) {&lt;br /&gt;
        var src = getImageSrc(img);&lt;br /&gt;
        var fileName = getFileNameFromSrc(src);&lt;br /&gt;
        var cacheKey;&lt;br /&gt;
        var entry;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return;&lt;br /&gt;
&lt;br /&gt;
        if (!fileName || !mw || !mw.loader) {&lt;br /&gt;
            callback(src);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        cacheKey = fileName + &#039;|&#039; + width;&lt;br /&gt;
        entry = thumbCache[cacheKey];&lt;br /&gt;
&lt;br /&gt;
        if (entry) {&lt;br /&gt;
            if (entry.resolved) {&lt;br /&gt;
                callback(entry.url || src);&lt;br /&gt;
            } else {&lt;br /&gt;
                entry.callbacks.push(callback);&lt;br /&gt;
            }&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        entry = {&lt;br /&gt;
            resolved: false,&lt;br /&gt;
            url: &#039;&#039;,&lt;br /&gt;
            callbacks: [callback]&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        thumbCache[cacheKey] = entry;&lt;br /&gt;
&lt;br /&gt;
        function finish(url) {&lt;br /&gt;
            var callbacks = entry.callbacks.slice();&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            entry.resolved = true;&lt;br /&gt;
            entry.url = url || src;&lt;br /&gt;
            entry.callbacks = [];&lt;br /&gt;
&lt;br /&gt;
            for (i = 0; i &amp;lt; callbacks.length; i++) {&lt;br /&gt;
                callbacks[i](entry.url);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using(&#039;mediawiki.api&#039;).done(function () {&lt;br /&gt;
            var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + fileName,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url&#039;,&lt;br /&gt;
                iiurlwidth: width,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).done(function (data) {&lt;br /&gt;
                var page;&lt;br /&gt;
                var info;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    data &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages.length&lt;br /&gt;
                ) {&lt;br /&gt;
                    page = data.query.pages[0];&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        page &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo.length&lt;br /&gt;
                    ) {&lt;br /&gt;
                        info = page.imageinfo[0];&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                finish((info &amp;amp;&amp;amp; (info.thumburl || info.url)) || src);&lt;br /&gt;
            }).fail(function () {&lt;br /&gt;
                finish(src);&lt;br /&gt;
            });&lt;br /&gt;
        }).fail(function () {&lt;br /&gt;
            finish(src);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySliceImages(frame, thumbUrl) {&lt;br /&gt;
        var slices;&lt;br /&gt;
        var i;&lt;br /&gt;
        var img;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !thumbUrl) return;&lt;br /&gt;
&lt;br /&gt;
        slices = frame.querySelectorAll(&#039;.crt-page-monitor-slice&#039;);&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; slices.length; i++) {&lt;br /&gt;
            slices[i].innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            img = document.createElement(&#039;img&#039;);&lt;br /&gt;
            img.className = &#039;crt-page-monitor-slice-img&#039;;&lt;br /&gt;
            img.src = thumbUrl;&lt;br /&gt;
            img.alt = &#039;&#039;;&lt;br /&gt;
            img.decoding = &#039;async&#039;;&lt;br /&gt;
            img.loading = &#039;eager&#039;;&lt;br /&gt;
            img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
            slices[i].appendChild(img);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        frame.setAttribute(&#039;data-crt-slices-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrame(frame) {&lt;br /&gt;
        var baseImg;&lt;br /&gt;
        var width;&lt;br /&gt;
&lt;br /&gt;
        if (!frame) return;&lt;br /&gt;
        if (frame.getAttribute(&#039;data-crt-slices-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        baseImg = frame.querySelector(&#039;.crt-page-monitor-image-base img&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!baseImg) return;&lt;br /&gt;
&lt;br /&gt;
        width = parseSliceWidth(frame.getAttribute(&#039;data-crt-slice-width&#039;));&lt;br /&gt;
&lt;br /&gt;
        resolveThumbUrl(baseImg, width, function (thumbUrl) {&lt;br /&gt;
            if (!frame || !frame.parentNode) return;&lt;br /&gt;
            applySliceImages(frame, thumbUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrames(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var frames = scope.querySelectorAll(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; frames.length; i++) {&lt;br /&gt;
            initBannerFrame(frames[i]);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initBannerFrames(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initBannerFrames($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(jQuery, window.mw);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Tab System — tab switching UI&lt;br /&gt;
   글리치 플리커 + RGB split + 방향 슬라이드&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function initDocTabs() {&lt;br /&gt;
        var tabBars = document.querySelectorAll(&#039;.doc-tab-bar&#039;);&lt;br /&gt;
        if (!tabBars.length) return;&lt;br /&gt;
&lt;br /&gt;
        tabBars.forEach(function (bar) {&lt;br /&gt;
            if (bar.getAttribute(&#039;data-tabs-init&#039;)) return;&lt;br /&gt;
            bar.setAttribute(&#039;data-tabs-init&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            var tabs = Array.from(bar.querySelectorAll(&#039;.doc-tab&#039;));&lt;br /&gt;
            if (!tabs.length) return;&lt;br /&gt;
&lt;br /&gt;
            var panel = bar.closest(&#039;.doc-panel&#039;);&lt;br /&gt;
            var display = panel ? panel.querySelector(&#039;.doc-display&#039;) : null;&lt;br /&gt;
            if (!display) display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
            if (!display) return;&lt;br /&gt;
&lt;br /&gt;
            tabs.forEach(function (tab, i) {&lt;br /&gt;
                tab.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
                    var currentIdx = tabs.findIndex(function (t) {&lt;br /&gt;
                        return t.classList.contains(&#039;active&#039;);&lt;br /&gt;
                    });&lt;br /&gt;
                    if (currentIdx === i) return;&lt;br /&gt;
                    switchTab(tabs, display, i, i &amp;gt; currentIdx ? 1 : -1);&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            var initIdx = tabs.findIndex(function (t) { return t.classList.contains(&#039;active&#039;); });&lt;br /&gt;
            if (initIdx !== -1) {&lt;br /&gt;
                var initRef = tabs[initIdx].dataset.ref;&lt;br /&gt;
                var initEl = initRef ? document.getElementById(initRef) : null;&lt;br /&gt;
                display.innerHTML = initEl ? initEl.innerHTML : (tabs[initIdx].dataset.content || &#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var isAnimating = false;&lt;br /&gt;
&lt;br /&gt;
    function switchTab(tabs, display, nextIdx, dir) {&lt;br /&gt;
        if (isAnimating) return;&lt;br /&gt;
        isAnimating = true;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (t) { t.classList.remove(&#039;active&#039;); });&lt;br /&gt;
        tabs[nextIdx].classList.add(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
        var ref = tabs[nextIdx].dataset.ref;&lt;br /&gt;
        var nextContent;&lt;br /&gt;
        if (ref) {&lt;br /&gt;
            var refEl = document.getElementById(ref);&lt;br /&gt;
            nextContent = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            nextContent = tabs[nextIdx].dataset.content || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        glitchOut(display, dir, function () {&lt;br /&gt;
            display.innerHTML = nextContent;&lt;br /&gt;
            glitchIn(display, dir, function () {&lt;br /&gt;
                isAnimating = false;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchOut(el, dir, cb) {&lt;br /&gt;
        var duration = 160;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var slideX = dir * 16;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = p * p;&lt;br /&gt;
&lt;br /&gt;
            var tx = slideX * ease;&lt;br /&gt;
            var skew = dir * ease * 1.0;&lt;br /&gt;
            var opacity = 1 - ease;&lt;br /&gt;
            var rgb = ease * 5;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.75)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.65)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + (1 + ease * 0.25) + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.opacity = &#039;0&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchIn(el, dir, cb) {&lt;br /&gt;
        var duration = 200;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var startX = -dir * 16;&lt;br /&gt;
&lt;br /&gt;
        el.style.transform = &#039;translateX(&#039; + startX + &#039;px) skewX(&#039; + (-dir * 1.0) + &#039;deg)&#039;;&lt;br /&gt;
        el.style.opacity = &#039;0&#039;;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = 1 - Math.pow(1 - p, 3);&lt;br /&gt;
&lt;br /&gt;
            var tx = startX * (1 - ease);&lt;br /&gt;
            var skew = -dir * 1.0 * (1 - ease);&lt;br /&gt;
            var opacity = ease;&lt;br /&gt;
            var rgb = (1 - ease) * 3;&lt;br /&gt;
            var brightness = 1 + (1 - ease) * 0.35;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.65)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.55)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + brightness + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.transform = &#039;&#039;;&lt;br /&gt;
                el.style.opacity = &#039;&#039;;&lt;br /&gt;
                el.style.filter = &#039;&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initDocTabs);&lt;br /&gt;
} else {&lt;br /&gt;
    initDocTabs();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        initDocTabs();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Section Switch — 좌측 섹션 전환&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;.doc-nav-item[data-section]&#039;, function () {&lt;br /&gt;
    var name = $(this).attr(&#039;data-section&#039;);&lt;br /&gt;
    var display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
    var titleEl = document.getElementById(&#039;doc-center-title&#039;);&lt;br /&gt;
    var tabBar = document.getElementById(&#039;doc-tab-bar-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!display) return;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section]&#039;).removeClass(&#039;active&#039;);&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section=&amp;quot;&#039; + name + &#039;&amp;quot;]&#039;).addClass(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (name === &#039;text&#039;) {&lt;br /&gt;
        if (titleEl) titleEl.textContent = &#039;개요&#039;;&lt;br /&gt;
        if (tabBar) $(tabBar).show();&lt;br /&gt;
        var activeTab = tabBar ? tabBar.querySelector(&#039;.doc-tab.active&#039;) : null;&lt;br /&gt;
        if (!activeTab &amp;amp;&amp;amp; tabBar) activeTab = tabBar.querySelector(&#039;.doc-tab&#039;);&lt;br /&gt;
        if (activeTab) {&lt;br /&gt;
            var ref = activeTab.dataset.ref;&lt;br /&gt;
            var refEl = ref ? document.getElementById(ref) : null;&lt;br /&gt;
            display.innerHTML = refEl ? refEl.innerHTML : (activeTab.dataset.content || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        if (titleEl) titleEl.textContent = name === &#039;factions&#039; ? &#039;세력&#039; : name === &#039;people&#039; ? &#039;인물&#039; : name;&lt;br /&gt;
        if (tabBar) $(tabBar).hide();&lt;br /&gt;
        var refEl = document.getElementById(&#039;doc-content-&#039; + name);&lt;br /&gt;
        display.innerHTML = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   CRT WebGL Renderer — cool-retro-term IBM DOS style&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function createNoiseTexture(gl) {&lt;br /&gt;
        var size = 512;&lt;br /&gt;
        var data = new Uint8Array(size * size * 4);&lt;br /&gt;
        var s = 12345;&lt;br /&gt;
        function rand() {&lt;br /&gt;
            s = (s * 1664525 + 1013904223) &amp;amp; 0xffffffff;&lt;br /&gt;
            return (s &amp;gt;&amp;gt;&amp;gt; 0) / 0xffffffff;&lt;br /&gt;
        }&lt;br /&gt;
        for (var i = 0; i &amp;lt; data.length; i++) {&lt;br /&gt;
            data[i] = (rand() * 255) | 0;&lt;br /&gt;
        }&lt;br /&gt;
        var tex = gl.createTexture();&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, tex);&lt;br /&gt;
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
        return tex;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var VERT = [&lt;br /&gt;
        &#039;attribute vec2 a_pos;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5);&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_pos, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var FRAG = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_tex;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_noise;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_res;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_imgSize;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_noiseScale;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float sum2(vec2 v) { return v.x + v.y; }&#039;,&lt;br /&gt;
        &#039;float min2(vec2 v) { return min(v.x, v.y); }&#039;,&lt;br /&gt;
        &#039;float rgb2grey(vec3 v) { return dot(v, vec3(0.21, 0.72, 0.04)); }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 coverUV(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  float imgAR = u_imgSize.x / u_imgSize.y;&#039;,&lt;br /&gt;
        &#039;  float scrAR = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  float scale = imgAR / scrAR;&#039;,&lt;br /&gt;
        &#039;  float offsetY = (1.0 - scale) * 0.5;&#039;,&lt;br /&gt;
        &#039;  return vec2(uv.x, uv.y * scale + offsetY);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 barrel(vec2 v, vec2 cc, float k) {&#039;,&lt;br /&gt;
        &#039;  float ar = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  vec2 c2 = cc;&#039;,&lt;br /&gt;
        &#039;  if (ar &amp;gt; 1.0) c2.x /= ar; else c2.y *= ar;&#039;,&lt;br /&gt;
        &#039;  float dist = dot(c2, c2) * k;&#039;,&lt;br /&gt;
        &#039;  return v - cc * (1.0 + dist) * dist;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleInitialNoise(float t) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, vec2(fract(t/2048.0), fract(t/1048576.0)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleScreenNoise(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, u_noiseScale * uv);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyRgbShift(vec2 texUV, float shift) {&#039;,&lt;br /&gt;
        &#039;  vec2 d = vec2(shift, 0.0);&#039;,&lt;br /&gt;
        &#039;  vec3 r = texture2D(u_tex, clamp(texUV + d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 c = texture2D(u_tex, texUV).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 l = texture2D(u_tex, clamp(texUV - d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  return vec3(&#039;,&lt;br /&gt;
        &#039;    l.r*0.10 + r.r*0.30 + c.r*0.60,&#039;,&lt;br /&gt;
        &#039;    l.g*0.20 + r.g*0.20 + c.g*0.60,&#039;,&lt;br /&gt;
        &#039;    l.b*0.30 + r.b*0.10 + c.b*0.60&#039;,&lt;br /&gt;
        &#039;  );&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyBloom(vec2 texUV, float strength) {&#039;,&lt;br /&gt;
        &#039;  vec2 px = 2.0 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 acc = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0,  px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0, -px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  return acc / 6.0 * strength;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyScanlines(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
        &#039;  float line = mod(uv.y * u_res.y, 2.0);&#039;,&lt;br /&gt;
        &#039;  vec3 hi = ((1.0 + 0.30) - 0.2 * col) * col;&#039;,&lt;br /&gt;
        &#039;  vec3 lo = ((1.0 - 0.30) + 0.1 * col) * col;&#039;,&lt;br /&gt;
        &#039;  return line &amp;lt; 1.0 ? lo : hi;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
&#039;vec3 applyRasterization(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
&#039;  float t = u_time;&#039;,&lt;br /&gt;
&#039;  vec2 noiseUV = uv + vec2(fract(t * 0.030), fract(t * 0.060));&#039;,&lt;br /&gt;
&#039;  float wobbleX = (texture2D(u_noise, noiseUV * 0.8).r - 0.5) * 0.0018;&#039;,&lt;br /&gt;
&#039;  float wobbleY = (texture2D(u_noise, noiseUV * 0.8 + 0.5).r - 0.5) * 0.0008;&#039;,&lt;br /&gt;
&#039;  vec2 wobbledUV = clamp(uv + vec2(wobbleX, wobbleY), 0.0, 1.0);&#039;,&lt;br /&gt;
&#039;  vec3 wobbled = texture2D(u_tex, wobbledUV).rgb;&#039;,&lt;br /&gt;
&#039;  return mix(col, wobbled, 0.35);&#039;,&lt;br /&gt;
&#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float glowingLine(vec2 uv, float t) {&#039;,&lt;br /&gt;
&#039;  float pos = fract(t * 0.2);&#039;,&lt;br /&gt;
&#039;  float lineY = pos * (u_res.y + 330.0) - 120.0;&#039;,&lt;br /&gt;
        &#039;  float y = uv.y * u_res.y;&#039;,&lt;br /&gt;
        &#039;  return fract(smoothstep(-300.0, 0.0, y - lineY));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 applyHSync(vec2 uv, vec4 noise, float strength) {&#039;,&lt;br /&gt;
        &#039;  float randval = strength - noise.r;&#039;,&lt;br /&gt;
        &#039;  float scale = step(0.0, randval) * randval * strength;&#039;,&lt;br /&gt;
        &#039;  float freq = mix(4.0, 40.0, noise.g);&#039;,&lt;br /&gt;
        &#039;  uv.x += sin((uv.y + u_time * 0.001) * freq) * scale;&#039;,&lt;br /&gt;
        &#039;  return uv;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 cc = vec2(0.5) - v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float curvature = 0.18;&#039;,&lt;br /&gt;
        &#039;  vec2 uv = barrel(v_uv, cc, curvature);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float inScreen = min2(step(vec2(0.0), uv) - step(vec2(1.0), uv));&#039;,&lt;br /&gt;
        &#039;  if (inScreen &amp;lt; 0.5) { gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); return; }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 texUV = clamp(coverUV(uv), 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec4 initNoise = sampleInitialNoise(u_time);&#039;,&lt;br /&gt;
        &#039;  vec4 screenNoise = sampleScreenNoise(uv);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV = applyHSync(texUV, initNoise, 0.006);&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV += (vec2(screenNoise.b, screenNoise.a) - 0.5) * 0.0006;&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec3 col = applyRgbShift(texUV, 0.003);&#039;,&lt;br /&gt;
        &#039;  col += applyBloom(texUV, 0.22);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 bpx = 1.5 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 blurCol = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,   -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,    bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  col = mix(col, blurCol / 8.0, 0.40);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = applyScanlines(uv, col);&#039;,&lt;br /&gt;
        &#039;  col = applyRasterization(texUV, col);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float glow = glowingLine(uv, u_time);&#039;,&lt;br /&gt;
&#039;  col += glow * 0.08 * vec3(0.85, 0.95, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float dist = length(cc);&#039;,&lt;br /&gt;
        &#039;  col += screenNoise.a * 0.07 * (1.0 - dist * 1.3);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float grey = rgb2grey(col);&#039;,&lt;br /&gt;
        &#039;  vec3 phosphor = vec3(0.75, 0.88, 1.0);&#039;,&lt;br /&gt;
        &#039;  col = mix(col, grey * phosphor, 0.35);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 vig = v_uv * (1.0 - v_uv);&#039;,&lt;br /&gt;
        &#039;  col *= pow(vig.x * vig.y * 15.0, 0.25);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col *= 1.0 + (initNoise.g - 0.5) * 0.06;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col += vec3(0.012) * (1.0 - dist) * (1.0 - dist);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = pow(clamp(col, 0.0, 1.0), vec3(0.90));&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  gl_FragColor = vec4(col, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function initCRTCanvas(screen, imgEl) {&lt;br /&gt;
        var existing = screen.querySelector(&#039;.crt-webgl-canvas&#039;);&lt;br /&gt;
        if (existing) existing.remove();&lt;br /&gt;
&lt;br /&gt;
        var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.className = &#039;crt-webgl-canvas&#039;;&lt;br /&gt;
        canvas.style.cssText = &#039;position:absolute;inset:0;width:100%;height:100%;z-index:19;pointer-events:none;display:block;&#039;;&lt;br /&gt;
        screen.appendChild(canvas);&lt;br /&gt;
&lt;br /&gt;
        var gl = canvas.getContext(&#039;webgl&#039;) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
        if (!gl) return;&lt;br /&gt;
&lt;br /&gt;
        function compile(type, src) {&lt;br /&gt;
            var s = gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(s, src);&lt;br /&gt;
            gl.compileShader(s);&lt;br /&gt;
            if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {&lt;br /&gt;
                console.error(&#039;[CRT shader]&#039;, gl.getShaderInfoLog(s));&lt;br /&gt;
            }&lt;br /&gt;
            return s;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var prog = gl.createProgram();&lt;br /&gt;
        gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT));&lt;br /&gt;
        gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FRAG));&lt;br /&gt;
        gl.linkProgram(prog);&lt;br /&gt;
        gl.useProgram(prog);&lt;br /&gt;
&lt;br /&gt;
        var buf = gl.createBuffer();&lt;br /&gt;
        gl.bindBuffer(gl.ARRAY_BUFFER, buf);&lt;br /&gt;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);&lt;br /&gt;
        var aPos = gl.getAttribLocation(prog, &#039;a_pos&#039;);&lt;br /&gt;
        gl.enableVertexAttribArray(aPos);&lt;br /&gt;
        gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
        var uTex     = gl.getUniformLocation(prog, &#039;u_tex&#039;);&lt;br /&gt;
        var uNoise   = gl.getUniformLocation(prog, &#039;u_noise&#039;);&lt;br /&gt;
        var uRes     = gl.getUniformLocation(prog, &#039;u_res&#039;);&lt;br /&gt;
        var uImgSize = gl.getUniformLocation(prog, &#039;u_imgSize&#039;);&lt;br /&gt;
        var uTime    = gl.getUniformLocation(prog, &#039;u_time&#039;);&lt;br /&gt;
        var uNoiseSc = gl.getUniformLocation(prog, &#039;u_noiseScale&#039;);&lt;br /&gt;
&lt;br /&gt;
        var imgTex = gl.createTexture();&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE1);&lt;br /&gt;
        createNoiseTexture(gl);&lt;br /&gt;
&lt;br /&gt;
        var texReady = false;&lt;br /&gt;
        function uploadImg() {&lt;br /&gt;
            if (!imgEl || !imgEl.complete || !imgEl.naturalWidth) return;&lt;br /&gt;
            try {&lt;br /&gt;
                gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
                gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imgEl);&lt;br /&gt;
                texReady = true;&lt;br /&gt;
            } catch(e) { console.error(&#039;[CRT] tex:&#039;, e); }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var lastW = 0, lastH = 0;&lt;br /&gt;
        function resize() {&lt;br /&gt;
            var w = screen.offsetWidth, h = screen.offsetHeight;&lt;br /&gt;
            if (w === lastW &amp;amp;&amp;amp; h === lastH) return;&lt;br /&gt;
            lastW = w; lastH = h;&lt;br /&gt;
            canvas.width = w; canvas.height = h;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var raf;&lt;br /&gt;
        var t0 = performance.now();&lt;br /&gt;
&lt;br /&gt;
        function render() {&lt;br /&gt;
            raf = requestAnimationFrame(render);&lt;br /&gt;
            if (!texReady) { uploadImg(); return; }&lt;br /&gt;
            resize();&lt;br /&gt;
            var t = (performance.now() - t0) / 1000;&lt;br /&gt;
            gl.uniform1i(uTex, 0);&lt;br /&gt;
            gl.uniform1i(uNoise, 1);&lt;br /&gt;
            gl.uniform2f(uRes, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform2f(uImgSize, imgEl.naturalWidth, imgEl.naturalHeight);&lt;br /&gt;
            gl.uniform1f(uTime, t);&lt;br /&gt;
            gl.uniform2f(uNoiseSc, canvas.width / 512.0, canvas.height / 512.0);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) { uploadImg(); }&lt;br /&gt;
        else { imgEl.addEventListener(&#039;load&#039;, uploadImg); }&lt;br /&gt;
&lt;br /&gt;
        render();&lt;br /&gt;
        screen._crtCleanup = function () { cancelAnimationFrame(raf); };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initAllCRTScreens(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        scope.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (screen) {&lt;br /&gt;
            if (screen.getAttribute(&#039;data-crt-webgl&#039;) === &#039;1&#039;) return;&lt;br /&gt;
            screen.setAttribute(&#039;data-crt-webgl&#039;, &#039;1&#039;);&lt;br /&gt;
            var frame = screen.closest(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
            if (!frame) return;&lt;br /&gt;
            var imgEl = frame.querySelector(&#039;.crt-page-monitor-slice-img, .crt-page-monitor-image-base img&#039;);&lt;br /&gt;
            if (imgEl &amp;amp;&amp;amp; imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) {&lt;br /&gt;
                initCRTCanvas(screen, imgEl);&lt;br /&gt;
            } else if (imgEl) {&lt;br /&gt;
                imgEl.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, imgEl); });&lt;br /&gt;
            } else {&lt;br /&gt;
                var obs = new MutationObserver(function () {&lt;br /&gt;
                    var img = frame.querySelector(&#039;.crt-page-monitor-slice-img&#039;);&lt;br /&gt;
                    if (!img) return;&lt;br /&gt;
                    obs.disconnect();&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        initCRTCanvas(screen, img);&lt;br /&gt;
                    } else {&lt;br /&gt;
                        img.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, img); });&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
                obs.observe(frame, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () { initAllCRTScreens(document); });&lt;br /&gt;
&lt;br /&gt;
    if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($c) {&lt;br /&gt;
            document.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (s) {&lt;br /&gt;
                if (s._crtCleanup) s._crtCleanup();&lt;br /&gt;
                s.removeAttribute(&#039;data-crt-webgl&#039;);&lt;br /&gt;
            });&lt;br /&gt;
            initAllCRTScreens($c &amp;amp;&amp;amp; $c[0] ? $c[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Progress System UI&lt;br /&gt;
MediaWiki:Common.js controlled frontend&lt;br /&gt;
========================================= */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUiInitialized) return;&lt;br /&gt;
    window.ProgressSystemWebUiInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    var api = null;&lt;br /&gt;
&lt;br /&gt;
    function withApi(done, fail) {&lt;br /&gt;
        if (api) {&lt;br /&gt;
            done(api);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
            api = new mw.Api();&lt;br /&gt;
            done(api);&lt;br /&gt;
        }, function () {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var inFlightPageIds = new Set();&lt;br /&gt;
    var handledPageIds = new Set();&lt;br /&gt;
    var notificationQueue = [];&lt;br /&gt;
    var notificationActive = false;&lt;br /&gt;
    var summaryRequested = false;&lt;br /&gt;
    var currentSummary = null;&lt;br /&gt;
    var pendingSummary = null;&lt;br /&gt;
    var pendingOptions = null;&lt;br /&gt;
    var visibilityBound = false;&lt;br /&gt;
    var barTimerA = null;&lt;br /&gt;
    var barTimerB = null;&lt;br /&gt;
    var barTimerC = null;&lt;br /&gt;
    var summaryRetryTimer = null;&lt;br /&gt;
    var summaryRetryAttempts = 0;&lt;br /&gt;
&lt;br /&gt;
    function isLoggedIn() {&lt;br /&gt;
        return !!mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPageId() {&lt;br /&gt;
        var id = parseInt(mw.config.get(&#039;wgArticleId&#039;) || 0, 10);&lt;br /&gt;
        return Number.isFinite(id) ? id : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isRewardableClientSide() {&lt;br /&gt;
        if (!isLoggedIn()) return false;&lt;br /&gt;
        if (parseInt(mw.config.get(&#039;wgNamespaceNumber&#039;), 10) !== 0) return false;&lt;br /&gt;
        if (mw.config.get(&#039;wgIsMainPage&#039;)) return false;&lt;br /&gt;
        if (getPageId() &amp;lt;= 0) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPanelHtml() {&lt;br /&gt;
        return &#039;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;progress-panel&amp;quot; class=&amp;quot;profile-progress-block is-syncing&amp;quot; aria-live=&amp;quot;polite&amp;quot; data-progress-state=&amp;quot;syncing&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-title-row&amp;quot; hidden&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-level-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-level-label&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-gain&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-sub-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-xp-next&amp;quot;&amp;gt;SYNCING&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-daily-xp&amp;quot;&amp;gt;TODAY —&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-discovery-row&amp;quot;&amp;gt;DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDividerHtml() {&lt;br /&gt;
        /* 프로필 패널 최신 규칙: 레벨 패널과 버튼 영역 사이에 별도 나눔선은 만들지 않는다. */&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setPanelSync($panel) {&lt;br /&gt;
        if (!$panel || !$panel.length) return;&lt;br /&gt;
&lt;br /&gt;
        $panel.addClass(&#039;is-syncing&#039;).removeClass(&#039;is-max-level&#039;).attr(&#039;data-progress-state&#039;, &#039;syncing&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-title-row&#039;).text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text(&#039;SYNC&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(&#039;— XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(&#039;SYNCING&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-fill&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
        $panel.find(&#039;.progress-xp-gain&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function placePanel($panel) {&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $userBox = $right.children(&#039;.clbi-right-box&#039;).first();&lt;br /&gt;
        if (!$userBox.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $buttonArea = $userBox.children(&#039;.clbi-right-content&#039;).first();&lt;br /&gt;
        var $oldFallback = $panel.closest(&#039;.progress-panel-fallback&#039;);&lt;br /&gt;
&lt;br /&gt;
        if ($buttonArea.length) {&lt;br /&gt;
            var $divider = $(&#039;#profile-progress-divider&#039;);&lt;br /&gt;
&lt;br /&gt;
            $panel.insertBefore($buttonArea);&lt;br /&gt;
&lt;br /&gt;
            if (!$divider.length) {&lt;br /&gt;
                $divider = $(getDividerHtml());&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $divider.insertAfter($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;#profile-progress-divider&#039;).remove();&lt;br /&gt;
            $userBox.append($panel);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if ($oldFallback.length &amp;amp;&amp;amp; !$oldFallback.find(&#039;#progress-panel&#039;).length) {&lt;br /&gt;
            $oldFallback.remove();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensurePanel() {&lt;br /&gt;
        if (!isLoggedIn()) return $();&lt;br /&gt;
&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return $();&lt;br /&gt;
&lt;br /&gt;
        var $panel = $(&#039;#progress-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            $panel = $(getPanelHtml());&lt;br /&gt;
            if (!placePanel($panel)) return $();&lt;br /&gt;
            setPanelSync($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $panel.addClass(&#039;profile-progress-block&#039;);&lt;br /&gt;
            placePanel($panel);&lt;br /&gt;
&lt;br /&gt;
            if (!currentSummary &amp;amp;&amp;amp; $panel.attr(&#039;data-progress-state&#039;) !== &#039;syncing&#039;) {&lt;br /&gt;
                setPanelSync($panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $(&#039;#progress-panel&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clampPercent(value) {&lt;br /&gt;
        return Math.max(0, Math.min(100, value || 0));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasXpNotification(items) {&lt;br /&gt;
        if (!items || !items.length) return false;&lt;br /&gt;
        return items.some(function (item) {&lt;br /&gt;
            return item &amp;amp;&amp;amp; item.type === &#039;xp&#039; &amp;amp;&amp;amp; parseInt(item.amount || 0, 10) &amp;gt; 0;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearBarTimers() {&lt;br /&gt;
        [barTimerA, barTimerB, barTimerC].forEach(function (timer) {&lt;br /&gt;
            if (timer) clearTimeout(timer);&lt;br /&gt;
        });&lt;br /&gt;
        barTimerA = null;&lt;br /&gt;
        barTimerB = null;&lt;br /&gt;
        barTimerC = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBarInstant($fill, $gain, percent) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
        percent = clampPercent(percent);&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: percent + &#039;%&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;none&#039;, left: &#039;0%&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
        $fill.css({ transition: &#039;&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function animateGain($fill, $gain, fromPercent, toPercent, levelChanged) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
&lt;br /&gt;
        fromPercent = clampPercent(fromPercent);&lt;br /&gt;
        toPercent = clampPercent(toPercent);&lt;br /&gt;
&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: fromPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
        if (levelChanged) {&lt;br /&gt;
            var firstDelta = Math.max(0, 100 - fromPercent);&lt;br /&gt;
&lt;br /&gt;
            $gain.css({&lt;br /&gt;
                transition: &#039;none&#039;,&lt;br /&gt;
                opacity: firstDelta &amp;gt; 0 ? 1 : 0,&lt;br /&gt;
                left: fromPercent + &#039;%&#039;,&lt;br /&gt;
                width: firstDelta + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
            barTimerA = setTimeout(function () {&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 540ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: &#039;100%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 260);&lt;br /&gt;
&lt;br /&gt;
            barTimerB = setTimeout(function () {&lt;br /&gt;
                $fill.css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
                $gain.css({ transition: &#039;none&#039;, opacity: toPercent &amp;gt; 0 ? 1 : 0, left: &#039;0%&#039;, width: toPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
                if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 460ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: toPercent + &#039;%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 860);&lt;br /&gt;
&lt;br /&gt;
            barTimerC = setTimeout(function () {&lt;br /&gt;
                $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
            }, 1380);&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var delta = Math.max(0, toPercent - fromPercent);&lt;br /&gt;
&lt;br /&gt;
        if (delta &amp;lt;= 0.15) {&lt;br /&gt;
            setBarInstant($fill, $gain, toPercent);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $gain.css({&lt;br /&gt;
            transition: &#039;none&#039;,&lt;br /&gt;
            opacity: 1,&lt;br /&gt;
            left: fromPercent + &#039;%&#039;,&lt;br /&gt;
            width: delta + &#039;%&#039;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
        barTimerA = setTimeout(function () {&lt;br /&gt;
            $fill.css({&lt;br /&gt;
                transition: &#039;width 560ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                width: toPercent + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }, 260);&lt;br /&gt;
&lt;br /&gt;
        barTimerB = setTimeout(function () {&lt;br /&gt;
            $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
        }, 940);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updatePanel(summary, options) {&lt;br /&gt;
        if (!summary) return;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
&lt;br /&gt;
        var $panel = ensurePanel();&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            pendingSummary = $.extend({}, summary);&lt;br /&gt;
            pendingOptions = $.extend({}, options);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var level = summary.level || 1;&lt;br /&gt;
        var totalXp = summary.totalXp || 0;&lt;br /&gt;
        var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
        var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
        var percent = clampPercent(summary.progressPercent);&lt;br /&gt;
        var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
        var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
        var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
        var title = summary.equippedTitle || summary.title || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $panel.removeClass(&#039;is-syncing&#039;).toggleClass(&#039;is-max-level&#039;, isMaxLevel).attr(&#039;data-progress-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text((isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(totalXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;));&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY &#039; + dailyXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED &#039; + discoveries);&lt;br /&gt;
&lt;br /&gt;
        var $title = $panel.find(&#039;.progress-title-row&#039;);&lt;br /&gt;
        if (title) {&lt;br /&gt;
            $title.text(title).prop(&#039;hidden&#039;, false);&lt;br /&gt;
        } else {&lt;br /&gt;
            $title.text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $fill = $panel.find(&#039;.progress-xp-fill&#039;);&lt;br /&gt;
        var $gain = $panel.find(&#039;.progress-xp-gain&#039;);&lt;br /&gt;
        var animate = !!options.animateGain &amp;amp;&amp;amp; currentSummary &amp;amp;&amp;amp; totalXp &amp;gt; (currentSummary.totalXp || 0);&lt;br /&gt;
&lt;br /&gt;
        if (animate) {&lt;br /&gt;
            animateGain(&lt;br /&gt;
                $fill,&lt;br /&gt;
                $gain,&lt;br /&gt;
                clampPercent(currentSummary.progressPercent),&lt;br /&gt;
                percent,&lt;br /&gt;
                level !== (currentSummary.level || 1)&lt;br /&gt;
            );&lt;br /&gt;
        } else {&lt;br /&gt;
            setBarInstant($fill, $gain, percent);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        currentSummary = $.extend({}, summary);&lt;br /&gt;
        pendingSummary = null;&lt;br /&gt;
        pendingOptions = null;&lt;br /&gt;
        if (summaryRetryTimer) {&lt;br /&gt;
            clearTimeout(summaryRetryTimer);&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
        }&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearSummaryRetry() {&lt;br /&gt;
        if (summaryRetryTimer) clearTimeout(summaryRetryTimer);&lt;br /&gt;
        summaryRetryTimer = null;&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleSummaryRetry(delay) {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRetryTimer) return;&lt;br /&gt;
        if (summaryRetryAttempts &amp;gt;= 12) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRetryAttempts += 1;&lt;br /&gt;
        summaryRetryTimer = setTimeout(function () {&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, delay || 1800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSummary() {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRequested) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRequested = true;&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;progress_summary&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
                if (payload &amp;amp;&amp;amp; payload.available &amp;amp;&amp;amp; payload.summary) {&lt;br /&gt;
                    clearSummaryRetry();&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: false });&lt;br /&gt;
                } else {&lt;br /&gt;
                    scheduleSummaryRetry(2200);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                scheduleSummaryRetry(2200);&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                summaryRequested = false;&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            summaryRequested = false;&lt;br /&gt;
            scheduleSummaryRetry(2200);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queueNotifications(items) {&lt;br /&gt;
        if (!items || !items.length) return;&lt;br /&gt;
&lt;br /&gt;
        items.forEach(function (item) {&lt;br /&gt;
            if (!item) return;&lt;br /&gt;
            notificationQueue.push(item);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        showNextNotification();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function notificationText(item) {&lt;br /&gt;
        if (item.type === &#039;xp&#039;) {&lt;br /&gt;
            return &#039;+&#039; + (item.amount || 0) + &#039; XP · &#039; + (item.label || &#039;문서 열람&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;achievement&#039;) {&lt;br /&gt;
            var xp = item.amount ? &#039; · +&#039; + item.amount + &#039; XP&#039; : &#039;&#039;;&lt;br /&gt;
            return &#039;업적 달성 · &#039; + (item.label || &#039;새 업적&#039;) + xp;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;level&#039;) {&lt;br /&gt;
            return item.label || &#039;레벨 상승&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return item.label || &#039;보상 획득&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showNextNotification() {&lt;br /&gt;
        if (notificationActive) return;&lt;br /&gt;
        if (!notificationQueue.length) return;&lt;br /&gt;
&lt;br /&gt;
        notificationActive = true;&lt;br /&gt;
        var item = notificationQueue.shift();&lt;br /&gt;
        var $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$root.length) {&lt;br /&gt;
            $(&#039;body&#039;).append(&#039;&amp;lt;div id=&amp;quot;progress-toast-root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $toast = $(&#039;&amp;lt;div class=&amp;quot;progress-toast&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        $toast.text(notificationText(item));&lt;br /&gt;
        $root.append($toast);&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            $toast.addClass(&#039;is-visible&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            $toast.removeClass(&#039;is-visible&#039;);&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                $toast.remove();&lt;br /&gt;
                notificationActive = false;&lt;br /&gt;
                showNextNotification();&lt;br /&gt;
            }, 220);&lt;br /&gt;
        }, 2600);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPendingSummaryIfPossible() {&lt;br /&gt;
        if (!pendingSummary) return;&lt;br /&gt;
        updatePanel(pendingSummary, pendingOptions || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handlePageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (!isRewardableClientSide()) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var pageId = getPageId();&lt;br /&gt;
        if (handledPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (inFlightPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inFlightPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;progress_view&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2,&lt;br /&gt;
                errorformat: &#039;plaintext&#039;,&lt;br /&gt;
                pageid: pageId&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_view;&lt;br /&gt;
                if (!payload) return;&lt;br /&gt;
&lt;br /&gt;
                handledPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
                var animate = hasXpNotification(payload.notifications);&lt;br /&gt;
&lt;br /&gt;
                if (payload.summary) {&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: animate });&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (payload.notifications &amp;amp;&amp;amp; payload.notifications.length) {&lt;br /&gt;
                    queueNotifications(payload.notifications);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                inFlightPageIds.delete(pageId);&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            inFlightPageIds.delete(pageId);&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindVisibilitySync() {&lt;br /&gt;
        if (visibilityBound) return;&lt;br /&gt;
        visibilityBound = true;&lt;br /&gt;
&lt;br /&gt;
        document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
            if (document.visibilityState === &#039;visible&#039;) {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bootProgressSystem(reason) {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (isRewardableClientSide()) {&lt;br /&gt;
            handlePageView();&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 350);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 1500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleSpaPageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                handlePageView();&lt;br /&gt;
            }, 80);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySummary(summary, options) {&lt;br /&gt;
        updatePanel(summary, options || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.ProgressSystemWebUi = {&lt;br /&gt;
        boot: bootProgressSystem,&lt;br /&gt;
        requestSummary: requestSummary,&lt;br /&gt;
        applySummary: applySummary,&lt;br /&gt;
        handlePageView: handlePageView,&lt;br /&gt;
        handleSpaPageView: handleSpaPageView,&lt;br /&gt;
        ensurePanel: ensurePanel&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        bindVisibilitySync();&lt;br /&gt;
        bootProgressSystem(&#039;documentReady&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* CLBI Nations / Historical Events year tabs&lt;br /&gt;
 * Mirrors the country information panel model:&lt;br /&gt;
 * active tab uses .is-active/aria-selected and inactive pages use hidden.&lt;br /&gt;
 */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function activateClbiNationsHistoryYear(panel, targetYear) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var pages;&lt;br /&gt;
&lt;br /&gt;
        if (!panel || !targetYear) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        pages = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-page[data-year-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
        if (!tabs.length || !pages.length) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (tab) {&lt;br /&gt;
            var active = tab.getAttribute(&#039;data-year&#039;) === targetYear;&lt;br /&gt;
            tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
            tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        pages.forEach(function (page) {&lt;br /&gt;
            var active = page.getAttribute(&#039;data-year-panel&#039;) === targetYear;&lt;br /&gt;
            page.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
            if (active) {&lt;br /&gt;
                page.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
            } else {&lt;br /&gt;
                page.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveClbiNationsHistoryYear(panel, direction) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var activeIndex;&lt;br /&gt;
        var nextIndex;&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!panel) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
        activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
            return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
        nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
        target = tabs[nextIndex].getAttribute(&#039;data-year&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (activateClbiNationsHistoryYear(panel, target)) {&lt;br /&gt;
            tabs[nextIndex].focus();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initClbiNationsHistoryYearTabs(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var panels = scope.querySelectorAll(&#039;.clbi-nations-history-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(panels, function (panel) {&lt;br /&gt;
            if (panel.getAttribute(&#039;data-clbi-history-tabs-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
            panel.setAttribute(&#039;data-clbi-history-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (activateClbiNationsHistoryYear(panel, tab.getAttribute(&#039;data-year&#039;))) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
                var handled = false;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsHistoryYear(panel, -1);&lt;br /&gt;
                else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsHistoryYear(panel, 1);&lt;br /&gt;
                else if (event.key === &#039;Home&#039;) handled = activateClbiNationsHistoryYear(panel, (panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;) || {}).getAttribute &amp;amp;&amp;amp; panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;).getAttribute(&#039;data-year&#039;));&lt;br /&gt;
                else if (event.key === &#039;End&#039;) {&lt;br /&gt;
                    var tabs = panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;);&lt;br /&gt;
                    var last = tabs[tabs.length - 1];&lt;br /&gt;
                    handled = last ? activateClbiNationsHistoryYear(panel, last.getAttribute(&#039;data-year&#039;)) : false;&lt;br /&gt;
                    if (handled) last.focus();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (handled) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                    event.stopPropagation();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.initClbiNationsHistoryYearTabs = initClbiNationsHistoryYearTabs;&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initClbiNationsHistoryYearTabs(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initClbiNationsHistoryYearTabs($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Decoration runtime renderer&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var REGISTRY_TITLE = &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
    var RENDERED_ATTR = &#039;data-wiki-decoration-rendered&#039;;&lt;br /&gt;
    var HOST_ATTR = &#039;data-wiki-decoration-host&#039;;&lt;br /&gt;
    var MAIN_PAGE_PLACEMENT = &#039;main-body-well&#039;;&lt;br /&gt;
    var MAIN_PAGE_TARGET = &#039;.main-portal .main-body-well&#039;;&lt;br /&gt;
    var runtimeToken = 0;&lt;br /&gt;
    var lastRegistry = null;&lt;br /&gt;
    var lastRenderedPageKey = &#039;&#039;;&lt;br /&gt;
    var scheduledRender = 0;&lt;br /&gt;
    var nationsPlacementObserver = null;&lt;br /&gt;
    var observedNationsStack = null;&lt;br /&gt;
    var mainPagePlacementObserver = null;&lt;br /&gt;
    var mainPagePlacementMutationObserver = null;&lt;br /&gt;
    var observedMainPagePortal = null;&lt;br /&gt;
    var scheduledMainPageRebase = 0;&lt;br /&gt;
    var pixelAssetCache = {};&lt;br /&gt;
    var pixelCanvasCache = {};&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentPageKey() {&lt;br /&gt;
        var raw = mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        return normalizePageName(raw) || raw || &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureMainPageBodyWell() {&lt;br /&gt;
        var portal;&lt;br /&gt;
        var topMount;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var well;&lt;br /&gt;
        var manifesto;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
        portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        if (!portal) return null;&lt;br /&gt;
&lt;br /&gt;
        topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) {&lt;br /&gt;
                    topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                } else {&lt;br /&gt;
                    topMount.parentNode.appendChild(panel);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) {&lt;br /&gt;
            well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return well;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function cssAttrEscape(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).replace(/\\/g, &#039;\\\\&#039;).replace(/&amp;quot;/g, &#039;\\&amp;quot;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEra() {&lt;br /&gt;
        var content = document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var globe;&lt;br /&gt;
&lt;br /&gt;
        if (content) return content.getAttribute(&#039;data-era-content&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.querySelector(&#039;.clbi-nations-era-title-plate.is-active[data-era]&#039;);&lt;br /&gt;
        if (title) return title.getAttribute(&#039;data-era&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe]&#039;);&lt;br /&gt;
        if (globe) {&lt;br /&gt;
            return globe.getAttribute(&#039;data-current-era&#039;) || globe.getAttribute(&#039;data-nations-current-era&#039;) || globe.getAttribute(&#039;data-era-year&#039;) || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEraPanel() {&lt;br /&gt;
        var era = getActiveNationsEra();&lt;br /&gt;
        var selector;&lt;br /&gt;
        if (!era) return document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        selector = &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;]&#039;;&lt;br /&gt;
        return document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsContinent() {&lt;br /&gt;
        var eraPanel = getActiveNationsEraPanel();&lt;br /&gt;
        var root = eraPanel || document;&lt;br /&gt;
        var tab = root.querySelector(&#039;.clbi-nations-tabpanel-tab.is-active[data-continent], .clbi-nations-tabpanel-tab[aria-selected=&amp;quot;true&amp;quot;][data-continent]&#039;);&lt;br /&gt;
        var panel;&lt;br /&gt;
&lt;br /&gt;
        if (tab) return tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        panel = root.querySelector(&#039;.clbi-nations-tabpanel-continent.is-active[data-continent-panel]&#039;);&lt;br /&gt;
        if (panel) return panel.getAttribute(&#039;data-continent-panel&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationNationsBodySelector(era) {&lt;br /&gt;
        if (era) {&lt;br /&gt;
            return &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;] .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden]) .clbi-nations-tabpanel-body, .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Decoration semantic placement resolver&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    장식 저장 데이터의 placement 값은 &amp;quot;사용자가 고른 의미상 위치&amp;quot;를 나타낸다.&lt;br /&gt;
    resolver는 그 의미값을 실제 DOM 부착 위치와 표시 조건으로 번역한다.&lt;br /&gt;
&lt;br /&gt;
    예: 시대 문서에서 사용자가 1950년 / 아메리카를 지정하면 의미상 scope는&lt;br /&gt;
    그 조합이지만, 이미지를 붙일 기준면은 대륙 패널 자체가 아니라&lt;br /&gt;
    .clbi-nations-tabpanel-body이다. 따라서 placement=nations-continent-body는&lt;br /&gt;
    .clbi-nations-tabpanel-body에 이미지를 붙이고, 현재 활성 연도와 대륙이 저장값과&lt;br /&gt;
    일치할 때만 렌더링한다.&lt;br /&gt;
&lt;br /&gt;
    유지보수 규칙:&lt;br /&gt;
    - 새 조합형 문서가 생기면 entry.target을 매번 특수하게 저장하지 말고 placement를 추가한다.&lt;br /&gt;
    - DevTools.js 에디터 미리보기와 Common.js 런타임 렌더러의 resolver는 같은 의미를 가져야 한다.&lt;br /&gt;
    - target은 물리적 기준면, era/continent 같은 필드는 표시 조건으로 다룬다.&lt;br /&gt;
    */&lt;br /&gt;
    function shouldUseNationsBodyPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var target = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!document.querySelector(&#039;.clbi-nations-panel-stack&#039;)) return false;&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) return true;&lt;br /&gt;
        if (era || continent) return true;&lt;br /&gt;
        if (target.indexOf(&#039;clbi-nations-tabpanel-continent&#039;) !== -1) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveDecorationPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() || &#039;.liberty-content-main&#039;;&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!placement &amp;amp;&amp;amp; shouldUseNationsBodyPlacement(entry)) {&lt;br /&gt;
            placement = &#039;nations-continent-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;boot-gate&#039; || placement === &#039;loading-screen&#039;) {&lt;br /&gt;
            selector = &#039;#boot-gate-screen .boot-gate-decoration-layer, #boot-gate-screen&#039;;&lt;br /&gt;
            target = document.querySelector(selector);&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: !!document.getElementById(&#039;boot-gate-screen&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) {&lt;br /&gt;
            selector = getDecorationNationsBodySelector(era);&lt;br /&gt;
            target = document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-tabpanel-body&#039;) || document.querySelector(String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim());&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: (!era || era === getActiveNationsEra()) &amp;amp;&amp;amp; (!continent || continent === getActiveNationsContinent())&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문의 일반 장식은 저장된 target과 관계없이 본문 우물 안에서 렌더링한다.&lt;br /&gt;
        우물이 overflow:hidden이므로 #1d1d1d 프레임 밖으로 나갈 수 없다.&lt;br /&gt;
        boot/loading 및 국가 패널 전용 placement는 위 분기에서 기존 동작을 유지한다.&lt;br /&gt;
        */&lt;br /&gt;
        var mainPageWell = ensureMainPageBodyWell();&lt;br /&gt;
        if (mainPageWell) {&lt;br /&gt;
            return {&lt;br /&gt;
                placement: MAIN_PAGE_PLACEMENT,&lt;br /&gt;
                target: mainPageWell,&lt;br /&gt;
                targetSelector: MAIN_PAGE_TARGET,&lt;br /&gt;
                visible: true&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            placement: placement,&lt;br /&gt;
            target: document.querySelector(selector),&lt;br /&gt;
            targetSelector: selector,&lt;br /&gt;
            visible: true&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeNumber(value, fallback) {&lt;br /&gt;
        var n = parseFloat(value);&lt;br /&gt;
        return Number.isFinite(n) ? n : fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBool(value, fallback) {&lt;br /&gt;
        if (value === true || value === &#039;true&#039; || value === &#039;1&#039; || value === 1) return true;&lt;br /&gt;
        if (value === false || value === &#039;false&#039; || value === &#039;0&#039; || value === 0) return false;&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeSrc(src) {&lt;br /&gt;
        src = String(src || &#039;&#039;).trim();&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(src) || src.charAt(0) === &#039;/&#039; || src.indexOf(&#039;data:&#039;) === 0 || src.indexOf(&#039;blob:&#039;) === 0) return src;&lt;br /&gt;
        return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(src);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function normalizeAssetType(entry) {&lt;br /&gt;
        var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var ref = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (type === &#039;clbi-pixel-json&#039; || type === &#039;pixel-rle&#039; || type === &#039;pixel-json&#039;) return &#039;pixel-json&#039;;&lt;br /&gt;
        if (!type &amp;amp;&amp;amp; /\.json(?:[?#].*)?$/i.test(ref)) return &#039;pixel-json&#039;;&lt;br /&gt;
        return type || &#039;image&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPixelJsonDecoration(entry) {&lt;br /&gt;
        return normalizeAssetType(entry) === &#039;pixel-json&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPixelJsonRef(entry) {&lt;br /&gt;
        return String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelJsonUrl(ref) {&lt;br /&gt;
        ref = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        if (!ref) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(ref) || ref.charAt(0) === &#039;/&#039; || ref.indexOf(&#039;data:&#039;) === 0 || ref.indexOf(&#039;blob:&#039;) === 0) return ref;&lt;br /&gt;
        if (/^(?:file|파일):/i.test(ref)) return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(ref.replace(/^(?:file|파일):/i, &#039;&#039;));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            if (ref.indexOf(&#039;:&#039;) !== -1) {&lt;br /&gt;
                return mw.util.getUrl(ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
            }&lt;br /&gt;
            return mw.util.getUrl(&#039;MediaWiki:&#039; + ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return ref;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function parsePixelColor(value) {&lt;br /&gt;
        var text;&lt;br /&gt;
        var m;&lt;br /&gt;
        if (Array.isArray(value)) {&lt;br /&gt;
            return [&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[0]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[1]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[2]) || 0)),&lt;br /&gt;
                value.length &amp;gt; 3 ? Math.max(0, Math.min(255, Number(value[3]) || 0)) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        m = text.match(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i);&lt;br /&gt;
        if (!m) return [0, 0, 0, 0];&lt;br /&gt;
        text = m[1];&lt;br /&gt;
        if (text.length === 3 || text.length === 4) {&lt;br /&gt;
            return [&lt;br /&gt;
                parseInt(text.charAt(0) + text.charAt(0), 16),&lt;br /&gt;
                parseInt(text.charAt(1) + text.charAt(1), 16),&lt;br /&gt;
                parseInt(text.charAt(2) + text.charAt(2), 16),&lt;br /&gt;
                text.length === 4 ? parseInt(text.charAt(3) + text.charAt(3), 16) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        return [&lt;br /&gt;
            parseInt(text.slice(0, 2), 16),&lt;br /&gt;
            parseInt(text.slice(2, 4), 16),&lt;br /&gt;
            parseInt(text.slice(4, 6), 16),&lt;br /&gt;
            text.length === 8 ? parseInt(text.slice(6, 8), 16) : 255&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function decodePixelRle36(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var parts;&lt;br /&gt;
        var runs = [];&lt;br /&gt;
        var i;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
&lt;br /&gt;
        if (!text) return runs;&lt;br /&gt;
        parts = text.split(&#039;,&#039;);&lt;br /&gt;
        for (i = 0; i + 3 &amp;lt; parts.length; i += 4) {&lt;br /&gt;
            x = parseInt(parts[i], 36);&lt;br /&gt;
            y = parseInt(parts[i + 1], 36);&lt;br /&gt;
            len = parseInt(parts[i + 2], 36);&lt;br /&gt;
            colorIndex = parseInt(parts[i + 3], 36);&lt;br /&gt;
            if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(len) || !Number.isFinite(colorIndex)) continue;&lt;br /&gt;
            runs.push([x, y, len, colorIndex]);&lt;br /&gt;
        }&lt;br /&gt;
        return runs;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelAsset(doc) {&lt;br /&gt;
        var encoding = String(doc &amp;amp;&amp;amp; (doc.encoding || doc.e) || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var width = Math.round(Number(doc &amp;amp;&amp;amp; (doc.width || doc.w)) || 0);&lt;br /&gt;
        var height = Math.round(Number(doc &amp;amp;&amp;amp; (doc.height || doc.h)) || 0);&lt;br /&gt;
        var paletteSource = Array.isArray(doc &amp;amp;&amp;amp; doc.palette) ? doc.palette : (Array.isArray(doc &amp;amp;&amp;amp; doc.p) ? doc.p : []);&lt;br /&gt;
        var palette = paletteSource.map(parsePixelColor);&lt;br /&gt;
        var runs;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        CLBI Pixel Forge v0.2.3 compact format:&lt;br /&gt;
        - MediaWiki 단일 문서 크기 제한을 피하기 위해 사람이 읽기 쉬운 [[x,y,len,c], ...] 배열 대신&lt;br /&gt;
          base36 토큰 문자열을 쓴다.&lt;br /&gt;
        - 형식은 x,y,len,colorIndex를 4개 토큰 단위로 반복한 rle36이다.&lt;br /&gt;
        - 복원 후 좌표계는 기존 runs 배열과 완전히 같으며, 최종 CSS px에 1:1로 찍는다.&lt;br /&gt;
        */&lt;br /&gt;
        if ((encoding === &#039;rle36&#039; || encoding === &#039;clbi-rle36&#039;) &amp;amp;&amp;amp; typeof (doc &amp;amp;&amp;amp; doc.r) === &#039;string&#039;) {&lt;br /&gt;
            runs = decodePixelRle36(doc.r);&lt;br /&gt;
        } else {&lt;br /&gt;
            runs = Array.isArray(doc &amp;amp;&amp;amp; doc.runs) ? doc.runs : [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!width || !height || width &amp;lt; 1 || height &amp;lt; 1) throw new Error(&#039;invalid pixel decoration size&#039;);&lt;br /&gt;
        if (width &amp;gt; 8192 || height &amp;gt; 8192) throw new Error(&#039;pixel decoration too large&#039;);&lt;br /&gt;
        return {&lt;br /&gt;
            type: String(doc &amp;amp;&amp;amp; (doc.type || doc.t) || &#039;clbi-pixel-decoration&#039;),&lt;br /&gt;
            version: Number(doc &amp;amp;&amp;amp; (doc.version || doc.v)) || 1,&lt;br /&gt;
            encoding: encoding || &#039;runs&#039;,&lt;br /&gt;
            width: width,&lt;br /&gt;
            height: height,&lt;br /&gt;
            palette: palette,&lt;br /&gt;
            runs: runs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchPixelAsset(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelAssetCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {&lt;br /&gt;
            promise: fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
                .then(function (response) {&lt;br /&gt;
                    if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                    return response.json();&lt;br /&gt;
                })&lt;br /&gt;
                .then(normalizePixelAsset)&lt;br /&gt;
        };&lt;br /&gt;
        pixelAssetCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawPixelAssetToCanvas(canvas, asset) {&lt;br /&gt;
        var ctx;&lt;br /&gt;
        var image;&lt;br /&gt;
        var data;&lt;br /&gt;
        var i;&lt;br /&gt;
        var run;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var color;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
        var p;&lt;br /&gt;
        var k;&lt;br /&gt;
&lt;br /&gt;
        canvas.width = asset.width;&lt;br /&gt;
        canvas.height = asset.height;&lt;br /&gt;
        ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        ctx.imageSmoothingEnabled = false;&lt;br /&gt;
        image = ctx.createImageData(asset.width, asset.height);&lt;br /&gt;
        data = image.data;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; asset.runs.length; i += 1) {&lt;br /&gt;
            run = asset.runs[i];&lt;br /&gt;
            if (!Array.isArray(run) || run.length &amp;lt; 4) continue;&lt;br /&gt;
            x = Math.round(Number(run[0]) || 0);&lt;br /&gt;
            y = Math.round(Number(run[1]) || 0);&lt;br /&gt;
            len = Math.round(Number(run[2]) || 0);&lt;br /&gt;
            colorIndex = Math.round(Number(run[3]) || 0);&lt;br /&gt;
            color = asset.palette[colorIndex];&lt;br /&gt;
            if (!color || len &amp;lt;= 0 || y &amp;lt; 0 || y &amp;gt;= asset.height || x &amp;gt;= asset.width) continue;&lt;br /&gt;
            if (x &amp;lt; 0) {&lt;br /&gt;
                len += x;&lt;br /&gt;
                x = 0;&lt;br /&gt;
            }&lt;br /&gt;
            len = Math.min(len, asset.width - x);&lt;br /&gt;
            for (k = 0; k &amp;lt; len; k += 1) {&lt;br /&gt;
                p = ((y * asset.width) + x + k) * 4;&lt;br /&gt;
                data[p] = color[0];&lt;br /&gt;
                data[p + 1] = color[1];&lt;br /&gt;
                data[p + 2] = color[2];&lt;br /&gt;
                data[p + 3] = color[3];&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preparePixelCanvas(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelCanvasCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {};&lt;br /&gt;
        cached.promise = fetchPixelAsset(ref).then(function (asset) {&lt;br /&gt;
            var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            drawPixelAssetToCanvas(canvas, asset);&lt;br /&gt;
            canvas.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;data-decoration-asset-size&#039;, asset.width + &#039;x&#039; + asset.height);&lt;br /&gt;
            cached.canvas = canvas;&lt;br /&gt;
            cached.width = asset.width;&lt;br /&gt;
            cached.height = asset.height;&lt;br /&gt;
            return canvas;&lt;br /&gt;
        });&lt;br /&gt;
        pixelCanvasCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedPixelCanvasSync(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached = url ? pixelCanvasCache[url] : null;&lt;br /&gt;
        return cached &amp;amp;&amp;amp; cached.canvas ? cached.canvas : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queryDecorationTarget(selector) {&lt;br /&gt;
        selector = String(selector || &#039;&#039;).trim();&lt;br /&gt;
        if (!selector) return null;&lt;br /&gt;
        try {&lt;br /&gt;
            return document.querySelector(selector);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function usesCanonicalMainPageCoordinates(entry, actualTarget) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var declaredTarget;&lt;br /&gt;
&lt;br /&gt;
        if (placement === MAIN_PAGE_PLACEMENT) return true;&lt;br /&gt;
        if (!actualTarget || !selector) return false;&lt;br /&gt;
&lt;br /&gt;
        declaredTarget = queryDecorationTarget(selector);&lt;br /&gt;
        return declaredTarget === actualTarget;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationLocalPosition(entry, actualTarget) {&lt;br /&gt;
        var x = normalizeNumber(entry &amp;amp;&amp;amp; entry.x, 0);&lt;br /&gt;
        var y = normalizeNumber(entry &amp;amp;&amp;amp; entry.y, 0);&lt;br /&gt;
        var sourceX = x;&lt;br /&gt;
        var sourceY = y;&lt;br /&gt;
        var sourceSelector;&lt;br /&gt;
        var sourceTarget;&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var actualRect;&lt;br /&gt;
        var sourceOriginX;&lt;br /&gt;
        var sourceOriginY;&lt;br /&gt;
        var actualOriginX;&lt;br /&gt;
        var actualOriginY;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문 데코 좌표 보존&lt;br /&gt;
        -----------------------------------------&lt;br /&gt;
        저장된 x/y는 기존 entry.target 좌표계를 기준으로 만들어졌다.&lt;br /&gt;
        데코를 main-body-well 안으로 옮길 때 x/y를 그대로 적용하면&lt;br /&gt;
        더 아래에서 시작하는 새 좌표계 때문에 장식이 바닥으로 밀린다.&lt;br /&gt;
&lt;br /&gt;
        실제 화면상의 위치는 유지하고 부모만 우물로 바꾼다.&lt;br /&gt;
        기존 기준면의 padding-box 원점과 새 우물의 padding-box 원점 차이를&lt;br /&gt;
        정수 px로 더해 새 로컬 좌표를 만든다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            currentPageKey() === &#039;대문&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList.contains(&#039;main-body-well&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
            !usesCanonicalMainPageCoordinates(entry, actualTarget)&lt;br /&gt;
        ) {&lt;br /&gt;
            sourceSelector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() ||&lt;br /&gt;
                &#039;.liberty-content-main&#039;;&lt;br /&gt;
            sourceTarget = queryDecorationTarget(sourceSelector) ||&lt;br /&gt;
                document.querySelector(&#039;.liberty-content-main&#039;) ||&lt;br /&gt;
                document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (sourceTarget &amp;amp;&amp;amp; sourceTarget !== actualTarget) {&lt;br /&gt;
                sourceRect = sourceTarget.getBoundingClientRect();&lt;br /&gt;
                actualRect = actualTarget.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
                sourceOriginX = sourceRect.left + normalizeNumber(sourceTarget.clientLeft, 0);&lt;br /&gt;
                sourceOriginY = sourceRect.top + normalizeNumber(sourceTarget.clientTop, 0);&lt;br /&gt;
                actualOriginX = actualRect.left + normalizeNumber(actualTarget.clientLeft, 0);&lt;br /&gt;
                actualOriginY = actualRect.top + normalizeNumber(actualTarget.clientTop, 0);&lt;br /&gt;
&lt;br /&gt;
                x += Math.round(sourceOriginX - actualOriginX);&lt;br /&gt;
                y += Math.round(sourceOriginY - actualOriginY);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            x: Math.round(x),&lt;br /&gt;
            y: Math.round(y),&lt;br /&gt;
            sourceX: Math.round(sourceX),&lt;br /&gt;
            sourceY: Math.round(sourceY),&lt;br /&gt;
            sourceSelector: sourceSelector || &#039;&#039;&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecorationBaseStyle(node, entry, actualTarget) {&lt;br /&gt;
        var position = getDecorationLocalPosition(entry, actualTarget);&lt;br /&gt;
&lt;br /&gt;
        node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
        node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
        node.style.opacity = String(normalizeNumber(entry.opacity, 1));&lt;br /&gt;
        node.style.zIndex = String(Math.round(normalizeNumber(entry.zIndex, 0)));&lt;br /&gt;
        node.style.pointerEvents = String(entry.pointerEvents || &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (position.sourceSelector) {&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-coordinate-source&#039;, position.sourceSelector);&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-x&#039;, String(position.sourceX));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-y&#039;, String(position.sourceY));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (entry.blendMode) node.style.mixBlendMode = String(entry.blendMode);&lt;br /&gt;
        if (entry.filter) node.style.filter = String(entry.filter);&lt;br /&gt;
        if (entry.transform) node.style.transform = String(entry.transform);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDecorationNodeActiveForNationsState(node) {&lt;br /&gt;
        var era = node ? String(node.getAttribute(&#039;data-decoration-era&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        var continent = node ? String(node.getAttribute(&#039;data-decoration-continent&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; era !== getActiveNationsEra()) return false;&lt;br /&gt;
        if (continent &amp;amp;&amp;amp; continent !== getActiveNationsContinent()) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDecorationNodeVisibility(node, visible) {&lt;br /&gt;
        if (!node) return;&lt;br /&gt;
        visible = visible !== false;&lt;br /&gt;
        /* aria-hidden stays true because wiki decorations are purely visual. */&lt;br /&gt;
        if (node.hidden !== !visible) node.hidden = !visible;&lt;br /&gt;
        if (node.style.display !== (visible ? &#039;&#039; : &#039;none&#039;)) node.style.display = visible ? &#039;&#039; : &#039;none&#039;;&lt;br /&gt;
        node.setAttribute(&#039;data-decoration-visible&#039;, visible ? &#039;1&#039; : &#039;0&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateDecorationVisibility(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var count = 0;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            setDecorationNodeVisibility(node, isDecorationNodeActiveForNationsState(node));&lt;br /&gt;
            count += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return count;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasRenderedDecorations(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return !!(scope &amp;amp;&amp;amp; scope.querySelector &amp;amp;&amp;amp; scope.querySelector(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPixelJsonDecoration(entry, target, visible) {&lt;br /&gt;
        var ref = getPixelJsonRef(entry);&lt;br /&gt;
        var template;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var ctx;&lt;br /&gt;
        if (!ref) return false;&lt;br /&gt;
&lt;br /&gt;
        function makeCanvasFromTemplate(source) {&lt;br /&gt;
            var out = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            var outCtx;&lt;br /&gt;
            out.className = &#039;wiki-decoration wiki-decoration-pixel-json&#039;;&lt;br /&gt;
            out.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
            out.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-type&#039;, &#039;pixel-json&#039;);&lt;br /&gt;
            if (entry.placement) out.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
            if (entry.era) out.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
            if (entry.continent) out.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
            out.width = source.width;&lt;br /&gt;
            out.height = source.height;&lt;br /&gt;
            out.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            out.style.width = source.width + &#039;px&#039;;&lt;br /&gt;
            out.style.height = source.height + &#039;px&#039;;&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-size&#039;, source.width + &#039;x&#039; + source.height);&lt;br /&gt;
            outCtx = out.getContext(&#039;2d&#039;);&lt;br /&gt;
            outCtx.imageSmoothingEnabled = false;&lt;br /&gt;
            outCtx.drawImage(source, 0, 0);&lt;br /&gt;
            applyDecorationBaseStyle(out, entry, target);&lt;br /&gt;
            setDecorationNodeVisibility(out, visible);&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template = getPreparedPixelCanvasSync(ref);&lt;br /&gt;
        if (template) {&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(template));&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Fallback path only.  A full entry pack should prepare the template before the&lt;br /&gt;
           normal UI is released, so users should not see a blank decoration canvas. */&lt;br /&gt;
        preparePixelCanvas(ref).then(function (source) {&lt;br /&gt;
            if (!target || !target.parentNode || !source) return;&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(source));&lt;br /&gt;
        }).catch(function () {});&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preloadPixelAssetsForRegistry(registry) {&lt;br /&gt;
        var promises = [];&lt;br /&gt;
        decorationList(registry).forEach(function (entry) {&lt;br /&gt;
            var ref;&lt;br /&gt;
            if (!entry || !matchesPage(entry) || !isPixelJsonDecoration(entry)) return;&lt;br /&gt;
            ref = getPixelJsonRef(entry);&lt;br /&gt;
            if (!ref) return;&lt;br /&gt;
            promises.push(preparePixelCanvas(ref).catch(function () { return null; }));&lt;br /&gt;
        });&lt;br /&gt;
        return Promise.all(promises);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decorationList(registry) {&lt;br /&gt;
        if (!registry || typeof registry !== &#039;object&#039;) return [];&lt;br /&gt;
        if (Array.isArray(registry)) return registry;&lt;br /&gt;
        if (Array.isArray(registry.decorations)) return registry.decorations;&lt;br /&gt;
        return [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesPage(entry) {&lt;br /&gt;
        var page = currentPageKey();&lt;br /&gt;
        var underscored = page.replace(/ /g, &#039;_&#039;);&lt;br /&gt;
        var pages = entry &amp;amp;&amp;amp; entry.pages;&lt;br /&gt;
        var target = entry &amp;amp;&amp;amp; entry.page;&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        if (normalizeBool(entry &amp;amp;&amp;amp; entry.global, false)) return true;&lt;br /&gt;
        if (String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;boot-gate&#039; || String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;loading-screen&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (normalizePageName(target).toLowerCase() === &#039;__boot__&#039; || normalizePageName(target).toLowerCase() === &#039;loading-screen&#039;) return true;&lt;br /&gt;
        if (!target &amp;amp;&amp;amp; !pages) return true;&lt;br /&gt;
&lt;br /&gt;
        if (Array.isArray(pages)) {&lt;br /&gt;
            for (i = 0; i &amp;lt; pages.length; i += 1) {&lt;br /&gt;
                if (normalizePageName(pages[i]) === page || String(pages[i] || &#039;&#039;) === underscored) return true;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        target = normalizePageName(target);&lt;br /&gt;
        return target === page || target === underscored;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearRendered(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureHost(target) {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (!target) return;&lt;br /&gt;
        target.setAttribute(HOST_ATTR, &#039;1&#039;);&lt;br /&gt;
        style = window.getComputedStyle ? window.getComputedStyle(target) : null;&lt;br /&gt;
        if (style &amp;amp;&amp;amp; style.position === &#039;static&#039;) target.style.position = &#039;relative&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecoration(entry) {&lt;br /&gt;
        var resolved;&lt;br /&gt;
        var target;&lt;br /&gt;
        var src;&lt;br /&gt;
        var img;&lt;br /&gt;
        var width;&lt;br /&gt;
        var height;&lt;br /&gt;
&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039; || normalizeBool(entry.enabled, true) === false) return false;&lt;br /&gt;
        resolved = resolveDecorationPlacement(entry);&lt;br /&gt;
        if (!resolved) return false;&lt;br /&gt;
        target = resolved.target;&lt;br /&gt;
        if (!target) return false;&lt;br /&gt;
&lt;br /&gt;
        ensureHost(target);&lt;br /&gt;
&lt;br /&gt;
        if (isPixelJsonDecoration(entry)) {&lt;br /&gt;
            return applyPixelJsonDecoration(entry, target, resolved.visible !== false);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        src = normalizeSrc(entry.src);&lt;br /&gt;
        if (!src) return false;&lt;br /&gt;
&lt;br /&gt;
        img = document.createElement(&#039;img&#039;);&lt;br /&gt;
        img.className = &#039;wiki-decoration&#039;;&lt;br /&gt;
        img.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
        img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, &#039;&#039;);&lt;br /&gt;
        img.setAttribute(&#039;decoding&#039;, &#039;async&#039;);&lt;br /&gt;
        img.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        img.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
        if (entry.placement) img.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
        if (entry.era) img.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
        if (entry.continent) img.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
        img.src = src;&lt;br /&gt;
&lt;br /&gt;
        applyDecorationBaseStyle(img, entry, target);&lt;br /&gt;
        width = normalizeNumber(entry.width, NaN);&lt;br /&gt;
        height = normalizeNumber(entry.height, NaN);&lt;br /&gt;
        if (Number.isFinite(width) &amp;amp;&amp;amp; width &amp;gt; 0) img.style.width = width + &#039;px&#039;;&lt;br /&gt;
        if (Number.isFinite(height) &amp;amp;&amp;amp; height &amp;gt; 0) img.style.height = height + &#039;px&#039;;&lt;br /&gt;
        if (entry.objectFit) img.style.objectFit = String(entry.objectFit);&lt;br /&gt;
        setDecorationNodeVisibility(img, resolved.visible !== false);&lt;br /&gt;
&lt;br /&gt;
        target.appendChild(img);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mainPageLayoutSignature() {&lt;br /&gt;
        var source = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
        var well = ensureMainPageBodyWell();&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var wellRect;&lt;br /&gt;
&lt;br /&gt;
        if (!source || !well) return &#039;&#039;;&lt;br /&gt;
        sourceRect = source.getBoundingClientRect();&lt;br /&gt;
        wellRect = well.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            Math.round((wellRect.left + normalizeNumber(well.clientLeft, 0)) - (sourceRect.left + normalizeNumber(source.clientLeft, 0))),&lt;br /&gt;
            Math.round((wellRect.top + normalizeNumber(well.clientTop, 0)) - (sourceRect.top + normalizeNumber(source.clientTop, 0))),&lt;br /&gt;
            Math.round(wellRect.width),&lt;br /&gt;
            Math.round(wellRect.height)&lt;br /&gt;
        ].join(&#039;|&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMainPageDecorationLayoutReady() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var panel = portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var topMount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!portal || !panel || !ensureMainPageBodyWell()) return false;&lt;br /&gt;
        if (!document.body || !document.body.classList.contains(&#039;clbi-main-page&#039;)) return false;&lt;br /&gt;
        if (topMount &amp;amp;&amp;amp; !topMount.querySelector(&#039;.portal-category-nav&#039;)) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForMainPageDecorationLayout() {&lt;br /&gt;
        var started;&lt;br /&gt;
        var previous = &#039;&#039;;&lt;br /&gt;
        var stableFrames = 0;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return Promise.resolve(true);&lt;br /&gt;
        started = Date.now();&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function check() {&lt;br /&gt;
                var signature = mainPageLayoutSignature();&lt;br /&gt;
&lt;br /&gt;
                if (signature &amp;amp;&amp;amp; signature === previous) stableFrames += 1;&lt;br /&gt;
                else stableFrames = 0;&lt;br /&gt;
                previous = signature;&lt;br /&gt;
&lt;br /&gt;
                if ((isMainPageDecorationLayoutReady() &amp;amp;&amp;amp; stableFrames &amp;gt;= 2) || Date.now() - started &amp;gt;= 1200) {&lt;br /&gt;
                    resolve(true);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (window.requestAnimationFrame) window.requestAnimationFrame(check);&lt;br /&gt;
                else window.setTimeout(check, 16);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebaseLegacyMainPageDecorations() {&lt;br /&gt;
        var well;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return;&lt;br /&gt;
        well = ensureMainPageBodyWell();&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-coordinate-source]&#039;).forEach(function (node) {&lt;br /&gt;
            var position = getDecorationLocalPosition({&lt;br /&gt;
                target: node.getAttribute(&#039;data-decoration-coordinate-source&#039;) || &#039;.liberty-content-main&#039;,&lt;br /&gt;
                x: normalizeNumber(node.getAttribute(&#039;data-decoration-source-x&#039;), 0),&lt;br /&gt;
                y: normalizeNumber(node.getAttribute(&#039;data-decoration-source-y&#039;), 0)&lt;br /&gt;
            }, well);&lt;br /&gt;
&lt;br /&gt;
            node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
            node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleMainPageDecorationRebase() {&lt;br /&gt;
        if (scheduledMainPageRebase) return;&lt;br /&gt;
        scheduledMainPageRebase = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindMainPagePlacementRefresh() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var nodes;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039; || !portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = null;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (observedMainPagePortal !== portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = portal;&lt;br /&gt;
&lt;br /&gt;
            if (typeof ResizeObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementObserver = new ResizeObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                nodes = [&lt;br /&gt;
                    document.querySelector(&#039;.liberty-content-main&#039;),&lt;br /&gt;
                    portal,&lt;br /&gt;
                    portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-panel&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-well&#039;)&lt;br /&gt;
                ];&lt;br /&gt;
                nodes.forEach(function (node) {&lt;br /&gt;
                    if (node) mainPagePlacementObserver.observe(node);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementMutationObserver = new MutationObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                mainPagePlacementMutationObserver.observe(portal, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        scheduleMainPageDecorationRebase();&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 80);&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderForCurrentLayout(registry, token) {&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return Promise.resolve(registry);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return waitForMainPageDecorationLayout().then(function () {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return registry;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(registry) {&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        lastRegistry = registry || { decorations: [] };&lt;br /&gt;
        lastRenderedPageKey = currentPageKey();&lt;br /&gt;
        bindNationsPlacementRefresh();&lt;br /&gt;
        clearRendered(document);&lt;br /&gt;
        decorationList(lastRegistry).forEach(function (entry) {&lt;br /&gt;
            if (token === runtimeToken &amp;amp;&amp;amp; matchesPage(entry)) applyDecoration(entry);&lt;br /&gt;
        });&lt;br /&gt;
        bindMainPagePlacementRefresh();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPrepared() {&lt;br /&gt;
        var registry = getPreparedRegistrySync() || lastRegistry;&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        if (!registry) return false;&lt;br /&gt;
        renderForCurrentLayout(registry, token);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function syncDecorationState() {&lt;br /&gt;
        if (lastRenderedPageKey === currentPageKey() &amp;amp;&amp;amp; hasRenderedDecorations(document)) {&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (lastRegistry) {&lt;br /&gt;
            renderForCurrentLayout(lastRegistry, runtimeToken);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        return renderPrepared();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleRenderFromCache() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleDecorationVisibilityUpdate() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindNationsPlacementRefresh() {&lt;br /&gt;
        var stack = document.querySelector(&#039;.clbi-nations-panel-stack&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!stack || observedNationsStack === stack) return;&lt;br /&gt;
        observedNationsStack = stack;&lt;br /&gt;
&lt;br /&gt;
        if (nationsPlacementObserver) {&lt;br /&gt;
            nationsPlacementObserver.disconnect();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
            nationsPlacementObserver = new MutationObserver(scheduleDecorationVisibilityUpdate);&lt;br /&gt;
            nationsPlacementObserver.observe(stack, {&lt;br /&gt;
                subtree: true,&lt;br /&gt;
                attributes: true,&lt;br /&gt;
                attributeFilter: [&#039;class&#039;, &#039;hidden&#039;, &#039;aria-selected&#039;, &#039;data-current-era&#039;, &#039;data-nations-current-era&#039;, &#039;data-era-year&#039;]&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedRegistrySync() {&lt;br /&gt;
        var registry = null;&lt;br /&gt;
        var url;&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.getJsonSync === &#039;function&#039;) {&lt;br /&gt;
                registry = window.EntryStore.getJsonSync(REGISTRY_TITLE);&lt;br /&gt;
                if (!registry &amp;amp;&amp;amp; mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                    url = mw.util.getUrl(REGISTRY_TITLE, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
                    registry = window.EntryStore.getJsonSync(url);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return registry &amp;amp;&amp;amp; typeof registry === &#039;object&#039; ? registry : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchRegistry() {&lt;br /&gt;
        var url;&lt;br /&gt;
        var prepared = getPreparedRegistrySync();&lt;br /&gt;
        if (prepared) return Promise.resolve(prepared);&lt;br /&gt;
        if (!mw || !mw.util || typeof fetch !== &#039;function&#039;) return Promise.resolve({ decorations: [] });&lt;br /&gt;
        url = mw.util.getUrl(REGISTRY_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
            .then(function (response) {&lt;br /&gt;
                if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                return response.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (!text || !text.trim()) return { decorations: [] };&lt;br /&gt;
                return JSON.parse(text);&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                return { decorations: [] };&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function reload() {&lt;br /&gt;
        var token;&lt;br /&gt;
        runtimeToken += 1;&lt;br /&gt;
        token = runtimeToken;&lt;br /&gt;
        return fetchRegistry().then(function (registry) {&lt;br /&gt;
            return preloadPixelAssetsForRegistry(registry).then(function () {&lt;br /&gt;
                return renderForCurrentLayout(registry, token);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
        var target = event.target &amp;amp;&amp;amp; event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent], [data-nations-era-move]&#039;) : null;&lt;br /&gt;
        if (target) window.setTimeout(scheduleDecorationVisibilityUpdate, 0);&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    function decorationDiagnostics() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: &#039;20260713-main-body-well-coordinate-003&#039;,&lt;br /&gt;
            hasRegistry: !!lastRegistry,&lt;br /&gt;
            entries: decorationList(lastRegistry).length,&lt;br /&gt;
            rendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            preparedRegistry: !!getPreparedRegistrySync(),&lt;br /&gt;
            pixelAssets: Object.keys(pixelAssetCache || {}).length,&lt;br /&gt;
            pixelCanvases: Object.keys(pixelCanvasCache || {}).length,&lt;br /&gt;
            visibilityOnlySync: true,&lt;br /&gt;
            mainPageCoordinateSurface: MAIN_PAGE_TARGET,&lt;br /&gt;
            mainPagePlacementObserved: !!observedMainPagePortal,&lt;br /&gt;
            lastRenderedPage: lastRenderedPageKey,&lt;br /&gt;
            visibleRendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-visible=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            scheduled: !!scheduledRender&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.Decorations = window.Decorations || {};&lt;br /&gt;
    window.Decorations.reload = reload;&lt;br /&gt;
    window.Decorations.render = render;&lt;br /&gt;
    window.Decorations.renderPrepared = renderPrepared;&lt;br /&gt;
    window.Decorations.sync = syncDecorationState;&lt;br /&gt;
    window.Decorations.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.Decorations.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.Decorations.clear = clearRendered;&lt;br /&gt;
    window.Decorations.apply = applyDecoration;&lt;br /&gt;
    window.Decorations.pageKey = currentPageKey;&lt;br /&gt;
    window.Decorations.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.Decorations.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.Decorations.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.Decorations.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_DECORATIONS = window.CLBI_DECORATIONS || {};&lt;br /&gt;
    window.CLBI_DECORATIONS.reload = reload;&lt;br /&gt;
    window.CLBI_DECORATIONS.render = render;&lt;br /&gt;
    window.CLBI_DECORATIONS.renderPrepared = renderPrepared;&lt;br /&gt;
    window.CLBI_DECORATIONS.sync = syncDecorationState;&lt;br /&gt;
    window.CLBI_DECORATIONS.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.CLBI_DECORATIONS.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.CLBI_DECORATIONS.clear = clearRendered;&lt;br /&gt;
    window.CLBI_DECORATIONS.apply = applyDecoration;&lt;br /&gt;
    window.CLBI_DECORATIONS.pageKey = currentPageKey;&lt;br /&gt;
    window.CLBI_DECORATIONS.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.CLBI_DECORATIONS.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.CLBI_DECORATIONS.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.CLBI_DECORATIONS.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, reload);&lt;br /&gt;
    } else {&lt;br /&gt;
        reload();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            reload();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Unified Shortcuts loader&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!window.mw || !mw.loader) return;&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Nation_List_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:History_Event_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Shortcuts.js&#039;);&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Main page manifesto bitmap renderer&lt;br /&gt;
   Integrated build: no extra raw-script page, no image/data-URL decoding.&lt;br /&gt;
   New shared systems do not use the legacy CLBI prefix.&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Retired compatibility block&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    선언문은 실제 HTML과 ManifestoIntro의 실선 요소만 사용한다.&lt;br /&gt;
    이 구식 렌더러는 제목·본문과 완성형 구분선을 canvas에 한 번에 그려&lt;br /&gt;
    새 애니메이션의 선 뒤에 트랙이 나타날 수 있었다.&lt;br /&gt;
&lt;br /&gt;
    기존 Common.js를 장기 캐시한 뒤 새 파일로 넘어오는 경우를 위해&lt;br /&gt;
    남아 있는 canvas와 상태만 제거하고 즉시 종료한다. 새 공개 이름에는&lt;br /&gt;
    프로젝트 접두사를 사용하지 않는 규칙을 유지한다.&lt;br /&gt;
    */&lt;br /&gt;
    function removeRetiredBitmap() {&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-bitmap&#039;),&lt;br /&gt;
            function (canvas) {&lt;br /&gt;
                if (canvas.parentNode) canvas.parentNode.removeChild(canvas);&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-inner&#039;),&lt;br /&gt;
            function (inner) {&lt;br /&gt;
                inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;data-bitmap-width&#039;,&lt;br /&gt;
                    &#039;data-bitmap-optical-shift-x&#039;,&lt;br /&gt;
                    &#039;data-bitmap-state&#039;,&lt;br /&gt;
                    &#039;data-bitmap-error&#039;&lt;br /&gt;
                ].forEach(function (name) {&lt;br /&gt;
                    inner.removeAttribute(name);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    removeRetiredBitmap();&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(removeRetiredBitmap);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreRetiredBitmapHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:&#039;retired-20260713-divider-no-track-001&#039;,&lt;br /&gt;
        recalculate:removeRetiredBitmap,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            return {&lt;br /&gt;
                integrated:false,&lt;br /&gt;
                state:&#039;retired&#039;,&lt;br /&gt;
                canvas:!!document.querySelector(&#039;.main-portal .main-manifesto-bitmap&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    var VERSION = &#039;20260712-bitmap18-inline-002&#039;;&lt;br /&gt;
    var SELECTOR = &#039;.main-portal .main-manifesto-inner&#039;;&lt;br /&gt;
    var CELL_W = 24;&lt;br /&gt;
    var CELL_H = 24;&lt;br /&gt;
    var GLYPHS = {&amp;quot;진&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,25056,25072,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;창&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,13280,78328,258272,12768,14128,13848,4360,3840,7040,12672,4480,8064,3840,0,0],&amp;quot;바&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12672,12672,14080,13056,13064,258456,127472,12784,12504,12408,12312,12288,12288,4096,0,0],&amp;quot;다&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,12792,12400,12320,258096,128528,14328,13048,12304,12288,12288,12288,4096,0,0],&amp;quot;는&amp;quot;:[19,0,0,0,0,0,0,96,192,192,14528,16064,960,0,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;왕&amp;quot;:[19,0,0,0,0,0,12288,28768,25568,25456,25392,25392,254944,24960,16064,10236,12344,7680,14080,12672,12672,16128,7936,0,0],&amp;quot;과&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,26560,26352,26112,26112,26208,255680,254912,29248,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;노&amp;quot;:[19,0,0,0,0,0,0,0,0,96,192,192,4288,15552,16320,7040,7168,3072,50688,131064,124,0,0,0,0],&amp;quot;예&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,24048,24504,23320,23320,24504,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;를&amp;quot;:[19,0,0,0,0,0,7168,16320,6144,8128,448,15552,1984,122880,262140,16636,8128,8128,3840,8064,1792,960,65408,4096,0],&amp;quot;같&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,110976,127168,12384,12344,4104,3584,0,8064,1920,256,14720,8064,0,0],&amp;quot;은&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,130944,115708,16,448,448,128,128,16256,7936,0,0],&amp;quot;깊&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,25024,24800,24624,8216,12288,16256,4864,15872,7680,31232,32640,256,0],&amp;quot;이&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,25072,25584,25368,25368,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;로&amp;quot;:[19,0,0,0,0,0,0,0,15360,16320,6272,7168,8128,384,14528,16320,7040,3072,50688,131064,124,0,0,0,0],&amp;quot;삼&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,78064,258272,13296,14136,13324,4096,16256,16256,4224,4480,8064,8064,0,0],&amp;quot;켰&amp;quot;:[19,0,0,0,0,0,12288,24576,26112,26600,29560,32656,25072,31992,26720,8224,8216,13056,15104,5056,15232,28544,58992,0,0],&amp;quot;고&amp;quot;:[19,0,0,0,0,0,0,0,0,7680,16352,12416,12288,12288,12544,13056,6912,4864,49920,131064,124,0,0,0,0],&amp;quot;,&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,8,0,0,0],&amp;quot;겨&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,32560,31104,24768,31840,31792,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;울&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,114688,262140,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;사&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12352,12480,12480,12480,12400,258272,127920,14104,13828,13312,12288,12288,12288,4096,0,0],&amp;quot;람&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13296,13240,78208,258552,12304,14224,13296,4096,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;짐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,26592,26160,9756,0,32512,32640,12672,12672,16128,16128,0,0],&amp;quot;승&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,130816,115708,272,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;의&amp;quot;:[19,0,0,0,0,0,28672,57344,24608,25536,26592,26160,26160,26464,25568,24576,24576,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;살&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,110832,127456,13232,13848,5132,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;을&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,32768,130944,115708,0,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;빛&amp;quot;:[19,0,0,0,0,0,12288,24576,25344,25344,26376,25496,25592,25584,25552,25088,7168,12288,32640,6400,7680,32256,25472,0,0],&amp;quot;으&amp;quot;:[19,0,0,0,0,0,0,0,128,3968,8064,4544,4288,6336,7616,3968,0,0,57344,131068,56,0,0,0,0],&amp;quot;얼&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25008,32536,31512,25016,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;렸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,32568,27008,31224,31792,26544,9200,8192,13056,15104,4928,15232,28544,58976,0,0],&amp;quot;.&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0],&amp;quot;그&amp;quot;:[19,0,0,0,0,0,0,0,0,7168,16352,12672,12288,12288,12288,12288,6144,4096,57344,131068,56,0,0,0,0],&amp;quot;들&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130048,118780,120,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;돌&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,8128,130048,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;벽&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25344,32520,31512,25560,32632,25584,9104,8192,31744,32640,8192,8192,8192,8192,0,0],&amp;quot;세&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22720,22720,22752,24432,22752,22960,23320,23300,22528,18432,16384,16384,24576,0,0],&amp;quot;우&amp;quot;:[19,0,0,0,0,0,0,384,7936,6528,12480,6336,7552,3968,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;땅&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,28560,9200,74160,254128,30936,16376,12280,12544,7936,13184,12672,12672,8064,3840,0,0],&amp;quot;찢&amp;quot;:[19,0,0,0,0,0,12288,24576,24704,26616,28656,26560,25456,28656,28056,26848,8192,32512,16128,7680,15872,25344,16768,0,0],&amp;quot;어&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,25520,32536,27416,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;길&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,24768,24672,24624,8216,16256,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;냈&amp;quot;:[19,0,0,0,0,0,8192,58368,19456,27672,27704,31792,31792,32560,28656,27120,8704,13824,14080,13248,15232,28544,50272,0,0],&amp;quot;며&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,26600,32536,25368,25400,32560,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;쇠&amp;quot;:[19,0,0,0,0,0,28672,57344,24704,24960,24960,25056,25536,26208,27888,24960,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;에&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,23024,23480,24344,24344,22968,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;제&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23488,23032,22976,24512,24184,23024,23344,23320,23052,18432,16384,16384,24576,0,0],&amp;quot;름&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,384,130560,116732,56,15872,16320,4288,4544,7552,8064,0,0],&amp;quot;새&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,27840,27840,31968,31856,23776,23984,20248,20228,16384,16384,16384,16384,24576,0,0],&amp;quot;겼&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,30712,32640,25024,31968,26672,8216,8200,15104,15104,6976,15232,28608,58992,0,0],&amp;quot;감&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,4120,4096,16256,16320,4224,4480,8064,8064,0,0],&amp;quot;옥&amp;quot;:[19,0,0,0,0,0,1792,7936,6528,6272,6528,8064,3584,1536,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;되&amp;quot;:[19,0,0,0,0,0,28672,57344,25088,26608,25072,24672,25632,26416,26608,25456,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;군&amp;quot;:[19,0,0,0,0,0,0,7680,16352,12288,12288,12288,6144,126976,262140,18172,1536,1728,1472,192,192,16256,8064,0,0],&amp;quot;대&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28032,28144,31856,31776,23600,24344,20440,19960,16400,16384,16384,16384,24576,0,0],&amp;quot;나&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12296,12312,12336,12304,259600,128784,14256,12528,12320,12288,12288,12288,4096,0,0],&amp;quot;르&amp;quot;:[19,0,0,0,0,0,0,0,6144,16320,6336,7168,8128,448,12480,16320,3968,0,57344,131068,56,0,0,0,0],&amp;quot;오&amp;quot;:[19,0,0,0,0,0,0,0,256,7936,15232,12672,12416,12672,8064,7936,3072,1024,49664,131064,124,0,0,0,0],&amp;quot;래&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28144,27064,32128,32248,23664,23568,20240,20464,16880,16384,16384,16384,24576,0,0],&amp;quot;된&amp;quot;:[19,0,0,0,0,0,28672,57344,26368,25584,24800,25696,28656,26608,29440,32736,25080,24832,25344,768,768,65280,15872,0,0],&amp;quot;원&amp;quot;:[19,0,0,0,0,0,28672,57440,25536,26464,26160,26160,29664,32640,26592,26620,25360,25024,25536,768,768,65280,15872,0,0],&amp;quot;한&amp;quot;:[19,0,0,0,0,0,6144,28704,12512,12800,14328,12784,258528,13088,12592,12784,12512,12416,4544,384,128,16256,7936,0,0],&amp;quot;함&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,14328,78032,258528,13104,12720,12784,4192,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;께&amp;quot;:[19,0,0,0,0,0,24576,55296,55296,55296,55488,57336,57280,57184,57184,55600,55704,55500,55392,49152,49152,16384,16384,0,0],&amp;quot;음&amp;quot;:[19,0,0,0,0,0,1920,8064,6528,6336,6336,3968,1792,32768,131040,16892,0,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;손&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,26304,1584,130560,249852,120,448,448,128,128,16256,7936,0,0],&amp;quot;넘&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,31792,31792,24624,28208,26608,8416,0,32512,32640,12672,12672,16256,15616,0,0],&amp;quot;갔&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,12312,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;\n&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],&amp;quot;줄&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,3840,15296,12480,41056,130976,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;처&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,25072,32448,27616,25392,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;부&amp;quot;:[19,0,0,0,0,0,6144,12288,28768,14400,15552,13248,8128,7872,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;터&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25056,24768,25344,25584,31984,31840,25648,28568,26360,24624,24576,24576,8192,8192,0,0],&amp;quot;목&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;둘&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130944,116732,1592,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;레&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23024,22968,22912,24568,22640,22544,23312,23536,23024,18432,16384,16384,24576,0,0],&amp;quot;있&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,25400,25368,25368,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;었&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32568,32536,25496,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;끝&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,3840,3584,8128,1920,384,16320,4032,0,0],&amp;quot;속&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,7936,12672,26336,1568,130560,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;뿌&amp;quot;:[19,0,0,0,0,0,4096,30720,14080,15904,16352,13280,16320,14208,4608,130944,126972,1552,1536,1536,1536,1536,512,0,0],&amp;quot;리&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,24960,25040,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;와&amp;quot;:[19,0,0,0,0,0,12288,28672,24608,25568,25456,26160,26416,25584,254432,254336,28864,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;빨&amp;quot;:[19,0,0,0,0,0,12288,28672,26112,26176,26304,255944,255992,10200,10224,1728,16256,16256,7936,7936,1792,896,65024,0,0],&amp;quot;굶&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,118784,262140,18428,1536,14080,16368,13792,14272,14272,16352,16352,0,0],&amp;quot;주&amp;quot;:[19,0,0,0,0,0,0,14336,16320,3264,1792,7936,12736,24672,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;림&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,25400,25472,25072,24624,26544,9200,8192,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;매&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28544,28644,32156,32024,23832,23992,19952,19504,16384,16384,16384,16384,24576,0,0],&amp;quot;였&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,32752,32568,25368,32664,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;른&amp;quot;:[19,0,0,0,0,0,4096,16320,6336,7744,4032,14528,16320,896,126976,253948,120,64,448,128,128,16256,7936,0,0],&amp;quot;아&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12304,12528,12720,13080,258840,127896,12784,12352,12288,12288,12288,12288,4096,0,0],&amp;quot;직&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,25568,26160,9752,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;태&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27840,28032,32240,31856,23600,24336,20440,20472,16432,16384,16384,16384,24576,0,0],&amp;quot;지&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25584,24992,24768,24816,25568,26160,27672,25612,24576,24576,24576,8192,0,0],&amp;quot;않&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,7168,6240,32352,15456,13408,14176,8160,0,0],&amp;quot;자&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13304,12720,12480,258296,127984,14128,13848,13324,12288,12288,12288,4096,0,0],&amp;quot;졌&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,32752,31136,32240,32736,26160,9756,8704,13056,15104,4928,15232,28544,50784,0,0],&amp;quot;앞&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,6144,8128,2432,7936,2816,15616,16320,128,0],&amp;quot;갈&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,127360,127168,12384,12344,4104,16256,8064,3840,8064,1792,896,65280,0,0],&amp;quot;때&amp;quot;:[19,0,0,0,0,0,24576,52224,55296,55296,57096,56312,63920,63632,63704,56536,57336,57336,49216,49152,49152,16384,16384,0,0],&amp;quot;마&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13284,13084,13080,258840,127792,12784,12464,12288,12288,12288,12288,4096,0,0],&amp;quot;듭&amp;quot;:[19,0,0,0,0,0,6144,16320,1984,384,12480,16064,3008,32768,131008,115708,14336,12352,12480,14720,16256,8064,7808,0,0],&amp;quot;파&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13248,12792,12688,13296,258528,127392,16352,12412,12288,12288,12288,12288,4096,0,0],&amp;quot;각&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,12672,258240,12384,12336,4120,4096,16128,13248,12288,4096,4096,4096,0,0],&amp;quot;쥔&amp;quot;:[19,0,0,0,0,0,28672,57856,26608,25584,25024,26592,28208,27664,28544,28668,25360,24960,25536,768,768,65280,15872,0,0],&amp;quot;당&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,12536,77920,258096,15888,14328,12920,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;기&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,25392,24960,24768,24672,24624,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;면&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26604,32536,25368,32560,31728,25072,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;풀&amp;quot;:[19,0,0,0,0,0,12288,16352,7296,8064,3328,32704,41440,131008,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;려&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,32696,31104,25040,31856,27696,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;날&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12312,12344,110640,128560,14256,13296,4192,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;것&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,24960,32704,26848,24624,24600,11272,3072,1792,3584,15104,29056,24800,0,0],&amp;quot;라&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13280,12728,12672,12792,258168,127504,13840,14320,12784,12288,12288,12288,4096,0,0],&amp;quot;여&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,32688,25368,25368,32664,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;므&amp;quot;:[19,0,0,0,0,0,0,0,4096,31744,32736,6624,3264,1728,16064,7360,0,0,57344,131068,56,0,0,0,0],&amp;quot;번&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25344,26376,26392,32728,25464,25584,25552,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;도&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,4064,960,384,14528,16064,7136,7360,3072,50688,131064,124,0,0,0,0],&amp;quot;느&amp;quot;:[19,0,0,0,0,0,0,0,0,32,192,192,192,14528,16064,7104,0,0,57344,131068,56,0,0,0,0],&amp;quot;슨&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,24768,48,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;해&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27968,28664,32240,32224,23984,23984,19952,19568,16384,16384,16384,16384,24576,0,0],&amp;quot;았&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12528,12784,78616,258840,13208,12784,12512,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;침&amp;quot;:[19,0,0,0,0,0,12288,24768,25024,25088,26592,25584,24800,25568,26416,9232,8,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;내&amp;quot;:[19,0,0,0,0,0,24576,58368,19456,19456,27656,27672,31792,31792,31760,24368,20464,19952,16384,16384,24576,24576,8192,0,0],&amp;quot;멎&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25576,32536,31512,25392,25072,8224,8192,32640,6912,3584,15872,25344,16768,0,0],&amp;quot;서&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,32496,24800,25520,26392,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;긴&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26368,25592,25520,25024,24768,24688,24632,24584,25472,8960,768,768,32512,15872,0,0],&amp;quot;붙&amp;quot;:[19,0,0,0,0,0,6144,12288,14432,16064,13248,7872,38912,130816,116732,1552,3840,3584,8064,1920,384,15808,4032,0,0],&amp;quot;잡&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,111040,258288,13280,13872,5660,4096,12352,12416,14720,16256,8064,7296,0,0],&amp;quot;피&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25592,25392,25568,25440,24992,28640,26744,24576,24576,24576,24576,8192,0,0],&amp;quot;가&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,13304,12728,12736,127168,258144,12336,12312,12300,12288,12288,12288,4096,0,0],&amp;quot;굴&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,126976,262140,18172,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;보&amp;quot;:[19,0,0,0,0,0,0,0,6144,12288,28768,14528,16064,13248,16320,7360,3072,1024,50688,131064,124,0,0,0,0],&amp;quot;하&amp;quot;:[19,0,0,0,0,0,6144,12288,12320,12512,12800,14332,12792,12512,259040,127792,12720,12528,12288,12288,12288,12288,4096,0,0],&amp;quot;입&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25584,25368,25368,25496,25072,8256,4096,28800,24960,14720,16128,16128,15616,0,0],&amp;quot;말&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13308,111384,127768,13240,12784,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;했&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,28664,27888,32224,23984,19888,18672,96,13824,15872,13184,15232,28544,50272,0,0],&amp;quot;조&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,8128,3072,1792,16128,28864,25184,1568,1536,50688,131064,124,0,0,0,0],&amp;quot;올&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6528,8064,3840,1536,128768,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;수&amp;quot;:[19,0,0,0,0,0,0,1536,3072,1792,1792,6912,29056,24800,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;록&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,3968,128512,253948,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;열&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,32688,31512,25368,32696,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;단&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,12792,12400,258096,13872,14232,14328,12336,12672,4480,384,384,16256,7936,0,0],&amp;quot;질&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25584,24992,25072,26464,26160,9240,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;히&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25024,26112,26616,25584,25024,25440,25392,25392,25056,24576,24576,24576,8192,8192,0,0],&amp;quot;운&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;데&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22912,23024,22640,24352,22576,23320,23512,23032,22544,18432,16384,16384,24576,0,0],&amp;quot;끌&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;린&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25568,25464,24960,25592,24624,26416,26608,24672,8320,9088,256,256,32512,15872,0,0],&amp;quot;게&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,22960,24000,24512,22624,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;누&amp;quot;:[19,0,0,0,0,0,0,96,192,192,192,15552,16320,960,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;인&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24624,25072,25400,25368,25368,25072,24800,24576,9088,8960,256,256,32512,15872,0,0],&amp;quot;쳐&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,32752,31936,25568,32560,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;문&amp;quot;:[19,0,0,0,0,0,12288,15872,15328,6336,3264,7872,7360,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;몸&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;식&amp;quot;:[19,0,0,0,0,0,12288,24576,24768,24960,25024,24816,24800,25568,26160,9244,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;억&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32536,32536,25368,25072,8416,0,31744,32640,12288,12288,12288,12288,0,0],&amp;quot;전&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25592,24992,31984,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;품&amp;quot;:[19,0,0,0,0,0,12288,16352,7360,8064,7040,16128,26592,127040,253948,18040,1536,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;발&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,111512,127992,13176,13272,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;머&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,25576,25368,32536,27448,25392,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;두&amp;quot;:[19,0,0,0,0,0,0,14336,8128,960,384,12480,16064,5056,192,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;닐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,24624,24624,27696,28464,26608,8288,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;격&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26616,32640,25024,31968,26672,8216,8200,32256,26496,8192,8192,8192,8192,0,0],&amp;quot;없&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25072,32536,31512,25496,25072,8256,8192,13824,15936,4928,15296,28608,51008,0,0],&amp;quot;니&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24584,24624,24624,24624,25648,28208,26544,25328,24576,24576,24576,8192,8192,0,0],&amp;quot;산&amp;quot;:[19,0,0,0,0,0,6144,28672,12352,12480,12480,12496,258272,13280,14128,13852,12288,12672,4480,384,384,16256,7936,0,0],&amp;quot;복&amp;quot;:[19,0,0,0,0,0,14336,12288,28736,15552,14272,16320,7808,3072,130560,249852,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;종&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,26208,1568,130816,116732,280,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;죽&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,24672,122912,262140,18172,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;망&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,78620,258840,13112,12784,12336,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;베&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22912,22912,24448,24448,24456,24536,23024,23024,22776,22648,18456,16384,16384,24576,0,0],&amp;quot;막&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,13084,258840,13080,12784,12464,4096,15872,16320,12288,4096,4096,4096,0,0],&amp;quot;무&amp;quot;:[19,0,0,0,0,0,0,30720,32608,15296,6336,3520,16320,15488,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;릎&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,8128,0,130560,116732,56,16320,3456,8064,3840,16128,14272,0,0],&amp;quot;꿇&amp;quot;:[19,0,0,0,0,0,0,14720,16368,12640,12672,12672,118912,262140,18428,1536,7936,7152,32736,14784,11712,16352,7648,0,0],&amp;quot;까&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,24576,24768,10232,10176,9824,254752,254768,8600,8388,8288,8192,8192,8192,12288,0,0],&amp;quot;놓&amp;quot;:[19,0,0,0,0,0,0,224,192,12480,15552,8128,7552,130048,253948,16760,1792,15872,16320,3584,6912,3328,1792,0,0],&amp;quot;러&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,29056,32720,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;재&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28608,28152,32192,31936,23672,24048,20272,20248,16908,16384,16384,16384,24576,0,0],&amp;quot;받&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,78744,259064,13176,13304,12672,0,7936,3584,768,24960,32640,1920,0,0],&amp;quot;묵&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,3200,130048,118780,1592,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;스&amp;quot;:[19,0,0,0,0,0,0,0,512,1536,3584,1920,1792,7936,12672,24800,0,0,57344,131068,56,0,0,0,0],&amp;quot;릴&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25592,25360,25536,25072,25136,26608,8432,14464,16256,7936,16128,3840,1792,130560,0,0],&amp;quot;계&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,24496,24256,22720,24416,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;구&amp;quot;:[19,0,0,0,0,0,0,6144,16352,12736,12288,12288,12288,6144,4096,131056,67324,1536,1536,1536,1536,1536,512,0,0],&amp;quot;물&amp;quot;:[19,0,0,0,0,0,14336,32544,15328,3264,7872,16064,32768,131040,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;못&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,7360,3776,16064,7296,3072,130816,116732,1048,3584,1536,1792,7936,12480,8304,0,0],&amp;quot;시&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,24816,24800,25520,26424,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;육&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6336,6528,3968,1536,130048,118780,6584,6528,16256,15296,4096,4096,4096,4096,0,0],&amp;quot;신&amp;quot;:[19,0,0,0,0,0,12288,24576,24640,25024,25024,24768,24800,25568,26416,25628,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;일&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25584,25368,25368,25528,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;모&amp;quot;:[19,0,0,0,0,0,0,0,14336,32256,16352,6368,3264,7872,16064,6144,2048,1024,50688,131064,124,0,0,0,0],&amp;quot;행&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,20472,27888,32224,32176,23984,19696,25184,15872,30208,25344,25344,16128,7680,0,0],&amp;quot;절&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,32160,32752,26464,26160,9752,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;멸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,32760,31512,25400,32688,25072,8224,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;끊&amp;quot;:[19,0,0,0,0,0,0,14592,16368,13280,12672,12672,12672,114688,262140,16636,7168,6240,32352,15456,13408,16224,7648,0,0],&amp;quot;순&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,131008,116732,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;간&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,13304,12688,258240,12384,12336,12312,12296,12672,4480,384,384,16256,7936,0,0]};&lt;br /&gt;
    var BODY_SCALE = 1;&lt;br /&gt;
    var TITLE_SCALE = 2;&lt;br /&gt;
    var BODY_TRACKING = -2;&lt;br /&gt;
    var TITLE_TRACKING = 0;&lt;br /&gt;
    var SPACE_ADVANCE = Math.round(CELL_H * 0.5);&lt;br /&gt;
    var BODY_LINE_HEIGHT = CELL_H + 8;&lt;br /&gt;
    var PARAGRAPH_GAP = 20;&lt;br /&gt;
    var DIVIDER_Y = 52;&lt;br /&gt;
    var BODY_TOP = 68;&lt;br /&gt;
    var BODY_COLOR = &#039;#d8d8d8&#039;;&lt;br /&gt;
    var TITLE_COLOR = &#039;#ffffff&#039;;&lt;br /&gt;
    var DIVIDER_COLOR = &#039;#555555&#039;;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var observed = typeof WeakSet === &#039;function&#039; ? new WeakSet() : null;&lt;br /&gt;
    var resizeObserver = typeof ResizeObserver === &#039;function&#039; ? new ResizeObserver(function (entries) {&lt;br /&gt;
        entries.forEach(function (entry) { schedule(entry.target, false); });&lt;br /&gt;
    }) : null;&lt;br /&gt;
&lt;br /&gt;
    function glyphFor(character) {&lt;br /&gt;
        return Object.prototype.hasOwnProperty.call(GLYPHS, character) ? GLYPHS[character] : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function measureText(text, scale, tracking) {&lt;br /&gt;
        var width = 0;&lt;br /&gt;
        var glyphCount = 0;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                width += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            width += glyph[0] * scale;&lt;br /&gt;
            width += tracking * scale;&lt;br /&gt;
            glyphCount += 1;&lt;br /&gt;
        });&lt;br /&gt;
        if (glyphCount &amp;amp;&amp;amp; tracking) width -= tracking * scale;&lt;br /&gt;
        return Math.max(0, Math.round(width));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function breakLongToken(token, maxWidth) {&lt;br /&gt;
        var pieces = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
        Array.from(token).forEach(function (character) {&lt;br /&gt;
            var candidate = current + character;&lt;br /&gt;
            if (current &amp;amp;&amp;amp; measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;gt; maxWidth) {&lt;br /&gt;
                pieces.push(current);&lt;br /&gt;
                current = character;&lt;br /&gt;
            } else {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        if (current) pieces.push(current);&lt;br /&gt;
        return pieces;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wrapParagraph(text, maxWidth) {&lt;br /&gt;
        var words = String(text || &#039;&#039;).trim().split(/\s+/).filter(Boolean);&lt;br /&gt;
        var lines = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        words.forEach(function (word) {&lt;br /&gt;
            var candidate = current ? current + &#039; &#039; + word : word;&lt;br /&gt;
            var pieces;&lt;br /&gt;
            if (measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (current) {&lt;br /&gt;
                lines.push(current);&lt;br /&gt;
                current = &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (measureText(word, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = word;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            pieces = breakLongToken(word, maxWidth);&lt;br /&gt;
            pieces.forEach(function (piece, index) {&lt;br /&gt;
                if (index === pieces.length - 1) current = piece;&lt;br /&gt;
                else lines.push(piece);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        if (current) lines.push(current);&lt;br /&gt;
        return lines.length ? lines : [&#039;&#039;];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateOpticalShift(lines, width) {&lt;br /&gt;
        var weightedCenter = 0;&lt;br /&gt;
        var totalWeight = 0;&lt;br /&gt;
        var maxLineWidth = 0;&lt;br /&gt;
        var shift;&lt;br /&gt;
        lines.forEach(function (line) {&lt;br /&gt;
            var lineWidth = line.width;&lt;br /&gt;
            var weight = Math.max(1, lineWidth) * BODY_LINE_HEIGHT;&lt;br /&gt;
            weightedCenter += (lineWidth / 2) * weight;&lt;br /&gt;
            totalWeight += weight;&lt;br /&gt;
            maxLineWidth = Math.max(maxLineWidth, lineWidth);&lt;br /&gt;
        });&lt;br /&gt;
        if (!totalWeight) return 0;&lt;br /&gt;
        shift = Math.round((width / 2) - (weightedCenter / totalWeight));&lt;br /&gt;
        return Math.max(0, Math.min(Math.max(0, width - maxLineWidth), shift));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawGlyph(context, glyph, x, y, scale) {&lt;br /&gt;
        var py;&lt;br /&gt;
        var px;&lt;br /&gt;
        var row;&lt;br /&gt;
        for (py = 0; py &amp;lt; CELL_H; py += 1) {&lt;br /&gt;
            row = glyph[py + 1] || 0;&lt;br /&gt;
            if (!row) continue;&lt;br /&gt;
            for (px = 0; px &amp;lt; CELL_W; px += 1) {&lt;br /&gt;
                if (row &amp;amp; (1 &amp;lt;&amp;lt; px)) {&lt;br /&gt;
                    context.fillRect(&lt;br /&gt;
                        Math.round(x + px * scale),&lt;br /&gt;
                        Math.round(y + py * scale),&lt;br /&gt;
                        scale,&lt;br /&gt;
                        scale&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawText(context, text, x, y, scale, tracking, color) {&lt;br /&gt;
        context.fillStyle = color;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                x += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            drawGlyph(context, glyph, x, y, scale);&lt;br /&gt;
            x += (glyph[0] + tracking) * scale;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sourceContent(inner) {&lt;br /&gt;
        var titleNode = inner.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        var paragraphs = [];&lt;br /&gt;
        Array.prototype.forEach.call(inner.children || [], function (child) {&lt;br /&gt;
            if (child &amp;amp;&amp;amp; child.tagName === &#039;P&#039;) {&lt;br /&gt;
                var value = String(child.textContent || &#039;&#039;).trim();&lt;br /&gt;
                if (value) paragraphs.push(value);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        return {&lt;br /&gt;
            title: titleNode ? String(titleNode.textContent || &#039;&#039;).trim() : &#039;&#039;,&lt;br /&gt;
            paragraphs: paragraphs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureCanvas(inner) {&lt;br /&gt;
        var canvas = inner.querySelector(&#039;.main-manifesto-bitmap&#039;);&lt;br /&gt;
        if (!canvas) {&lt;br /&gt;
            canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            canvas.className = &#039;main-manifesto-bitmap&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            canvas.setAttribute(&#039;data-bitmap-version&#039;, VERSION);&lt;br /&gt;
            inner.insertBefore(canvas, inner.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
        return canvas;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(inner, force) {&lt;br /&gt;
        var width;&lt;br /&gt;
        var source;&lt;br /&gt;
        var paragraphs;&lt;br /&gt;
        var flatLines = [];&lt;br /&gt;
        var titleWidth;&lt;br /&gt;
        var height;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var context;&lt;br /&gt;
        var y;&lt;br /&gt;
        var opticalShift;&lt;br /&gt;
&lt;br /&gt;
        if (!inner || !inner.isConnected) return;&lt;br /&gt;
        width = Math.max(1, Math.floor(inner.clientWidth || inner.getBoundingClientRect().width || 0));&lt;br /&gt;
        if (width &amp;lt;= 1) {&lt;br /&gt;
            window.setTimeout(function () { schedule(inner, true); }, 60);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (!force &amp;amp;&amp;amp; Number(inner.getAttribute(&#039;data-bitmap-width&#039;)) === width &amp;amp;&amp;amp; inner.classList.contains(&#039;is-bitmap-ready&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        source = sourceContent(inner);&lt;br /&gt;
        if (!source.title || !source.paragraphs.length) {&lt;br /&gt;
            fail(inner, new Error(&#039;Manifesto source nodes were not found.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        paragraphs = source.paragraphs.map(function (paragraph) {&lt;br /&gt;
            return wrapParagraph(paragraph, width).map(function (line) {&lt;br /&gt;
                var item = { text:line, width:measureText(line, BODY_SCALE, BODY_TRACKING) };&lt;br /&gt;
                flatLines.push(item);&lt;br /&gt;
                return item;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        titleWidth = measureText(source.title, TITLE_SCALE, TITLE_TRACKING);&lt;br /&gt;
        height = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, index) {&lt;br /&gt;
            height += lines.length * BODY_LINE_HEIGHT;&lt;br /&gt;
            if (index &amp;lt; paragraphs.length - 1) height += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
        height = Math.max(height, DIVIDER_Y + 2);&lt;br /&gt;
&lt;br /&gt;
        canvas = ensureCanvas(inner);&lt;br /&gt;
        canvas.width = width;&lt;br /&gt;
        canvas.height = Math.ceil(height);&lt;br /&gt;
        canvas.style.width = width + &#039;px&#039;;&lt;br /&gt;
        canvas.style.height = Math.ceil(height) + &#039;px&#039;;&lt;br /&gt;
        context = canvas.getContext(&#039;2d&#039;, { alpha:true });&lt;br /&gt;
        if (!context) {&lt;br /&gt;
            fail(inner, new Error(&#039;Canvas 2D context is unavailable.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        context.imageSmoothingEnabled = false;&lt;br /&gt;
        context.clearRect(0, 0, canvas.width, canvas.height);&lt;br /&gt;
&lt;br /&gt;
        drawText(&lt;br /&gt;
            context,&lt;br /&gt;
            source.title,&lt;br /&gt;
            Math.round((width - titleWidth) / 2),&lt;br /&gt;
            0,&lt;br /&gt;
            TITLE_SCALE,&lt;br /&gt;
            TITLE_TRACKING,&lt;br /&gt;
            TITLE_COLOR&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /* 완성형 구분선은 폐기했다. 실선 애니메이션은 ManifestoIntro만 담당한다. */&lt;br /&gt;
&lt;br /&gt;
        opticalShift = calculateOpticalShift(flatLines, width);&lt;br /&gt;
        y = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, paragraphIndex) {&lt;br /&gt;
            lines.forEach(function (line) {&lt;br /&gt;
                drawText(context, line.text, opticalShift, y, BODY_SCALE, BODY_TRACKING, BODY_COLOR);&lt;br /&gt;
                y += BODY_LINE_HEIGHT;&lt;br /&gt;
            });&lt;br /&gt;
            if (paragraphIndex &amp;lt; paragraphs.length - 1) y += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-width&#039;, String(width));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-optical-shift-x&#039;, String(opticalShift));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        inner.classList.add(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fail(inner, error) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;failed&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-error&#039;, String(error &amp;amp;&amp;amp; error.message ? error.message : error));&lt;br /&gt;
        try { console.error(&#039;[MainPageBitmap]&#039;, error); } catch (ignore) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(inner, force) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            try { render(inner, force); }&lt;br /&gt;
            catch (error) { fail(inner, error); }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mount(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var nodes = [];&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(SELECTOR)) nodes.push(scope);&lt;br /&gt;
        nodes = nodes.concat(Array.prototype.slice.call(scope.querySelectorAll(SELECTOR)));&lt;br /&gt;
        nodes.forEach(function (inner) {&lt;br /&gt;
            if (resizeObserver &amp;amp;&amp;amp; (!observed || !observed.has(inner))) {&lt;br /&gt;
                resizeObserver.observe(inner);&lt;br /&gt;
                if (observed) observed.add(inner);&lt;br /&gt;
            }&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function recalculate() {&lt;br /&gt;
        Array.prototype.forEach.call(document.querySelectorAll(SELECTOR), function (inner) {&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot() {&lt;br /&gt;
        mount(document);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 120);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) document.addEventListener(&#039;DOMContentLoaded&#039;, boot, { once:true });&lt;br /&gt;
    else boot();&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        window.clearTimeout(resizeTimer);&lt;br /&gt;
        resizeTimer = window.setTimeout(recalculate, 100);&lt;br /&gt;
    }, { passive:true });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                mount(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        recalculate:recalculate,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            var inner = document.querySelector(SELECTOR);&lt;br /&gt;
            return inner ? {&lt;br /&gt;
                integrated:true,&lt;br /&gt;
                state:inner.getAttribute(&#039;data-bitmap-state&#039;) || &#039;pending&#039;,&lt;br /&gt;
                error:inner.getAttribute(&#039;data-bitmap-error&#039;) || &#039;&#039;,&lt;br /&gt;
                width:inner.getAttribute(&#039;data-bitmap-width&#039;) || &#039;&#039;,&lt;br /&gt;
                opticalShiftX:inner.getAttribute(&#039;data-bitmap-optical-shift-x&#039;) || &#039;&#039;,&lt;br /&gt;
                canvas:!!inner.querySelector(&#039;.main-manifesto-bitmap&#039;)&lt;br /&gt;
            } : { integrated:true, state:&#039;not-mounted&#039; };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    window.MainPageManifesto = window.MainPageManifesto || {};&lt;br /&gt;
    window.MainPageManifesto.recalculate = recalculate;&lt;br /&gt;
})(window, document, window.mw);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4668</id>
		<title>미디어위키:CategoryNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4668"/>
		<updated>2026-07-17T02:35:52Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-display-fix-20260717 / js/CategoryNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - CategoryNav&lt;br /&gt;
대문 카테고리 SVG 네비 생성기&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var ITEMS = [&lt;br /&gt;
        { key:&#039;era&#039;, label:&#039;시대&#039;, title:&#039;시대&#039; },&lt;br /&gt;
        { key:&#039;settings&#039;, label:&#039;설정&#039;, title:&#039;설정&#039; },&lt;br /&gt;
        { key:&#039;project&#039;, label:&#039;프로젝트&#039;, title:&#039;프로젝트:소개&#039; },&lt;br /&gt;
        { key:&#039;corporations&#039;, label:&#039;기업 및 공동체&#039;, title:&#039;기업_및_공동체&#039; },&lt;br /&gt;
        { key:&#039;military&#039;, label:&#039;군, 정치집단&#039;, title:&#039;군_정치집단&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
    var BOUNDARY_DIRS = [0, 1, 1, -1, -1, 0];&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
&lt;br /&gt;
    function geometryApi() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) return window.buildWikiPath(title);&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearNode(node) {&lt;br /&gt;
        while (node &amp;amp;&amp;amp; node.firstChild) node.removeChild(node.firstChild);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateGeometry(width) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var frame = api.FRAME;&lt;br /&gt;
        var dimensions = api.DIMENSIONS;&lt;br /&gt;
        var contentLeft = frame.FRAME_FACE;&lt;br /&gt;
        var contentWidth = Math.max(ITEMS.length, width - frame.FRAME_FACE * 2);&lt;br /&gt;
        var boundaries = [];&lt;br /&gt;
        var segments = [];&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt;= ITEMS.length; i++) {&lt;br /&gt;
            boundaries.push(contentLeft + Math.round(contentWidth * i / ITEMS.length));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; ITEMS.length; i++) {&lt;br /&gt;
            var leftTopX = boundaries[i] + (i === 0 ? 0 : frame.GAP / 2);&lt;br /&gt;
            var rightTopX = boundaries[i + 1] - (i === ITEMS.length - 1 ? 0 : frame.GAP / 2);&lt;br /&gt;
            segments.push({&lt;br /&gt;
                leftTopX:leftTopX,&lt;br /&gt;
                rightTopX:rightTopX,&lt;br /&gt;
                leftDir:BOUNDARY_DIRS[i],&lt;br /&gt;
                rightDir:BOUNDARY_DIRS[i + 1],&lt;br /&gt;
                points:api.segmentAt(&lt;br /&gt;
                    leftTopX,&lt;br /&gt;
                    rightTopX,&lt;br /&gt;
                    dimensions.BUTTON_TOP,&lt;br /&gt;
                    dimensions.BUTTON_BOTTOM,&lt;br /&gt;
                    BOUNDARY_DIRS[i],&lt;br /&gt;
                    BOUNDARY_DIRS[i + 1],&lt;br /&gt;
                    0,&lt;br /&gt;
                    0,&lt;br /&gt;
                    0&lt;br /&gt;
                )&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            width:width,&lt;br /&gt;
            boundaries:boundaries,&lt;br /&gt;
            segments:segments,&lt;br /&gt;
            dimensions:dimensions&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendFrame(svg, geometry) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var frame = api.FRAME;&lt;br /&gt;
        var d = geometry.dimensions;&lt;br /&gt;
        var width = geometry.width;&lt;br /&gt;
        var group = api.create(&#039;g&#039;, &#039;portal-category-frame&#039;);&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(api.rect(&#039;portal-category-frame-edge&#039;, 0, 0, width, d.ROW_HEIGHT));&lt;br /&gt;
        group.appendChild(api.rect(&#039;portal-category-frame-surface&#039;, 1, 1, width - 2, d.ROW_HEIGHT - 2));&lt;br /&gt;
        group.appendChild(api.rect(&lt;br /&gt;
            &#039;portal-category-frame-well&#039;,&lt;br /&gt;
            frame.FRAME_FACE,&lt;br /&gt;
            frame.FRAME_FACE,&lt;br /&gt;
            width - frame.FRAME_FACE * 2,&lt;br /&gt;
            d.ROW_HEIGHT - frame.FRAME_FACE * 2&lt;br /&gt;
        ));&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(api.line(&#039;portal-category-frame-highlight&#039;, [1, 1], [width - 1, 1]));&lt;br /&gt;
        group.appendChild(api.line(&#039;portal-category-frame-highlight&#039;, [width - 1, 1], [width - 1, d.ROW_HEIGHT - 1]));&lt;br /&gt;
        group.appendChild(api.line(&#039;portal-category-frame-shadow&#039;, [1, d.ROW_HEIGHT - 1], [1, 1]));&lt;br /&gt;
        group.appendChild(api.line(&#039;portal-category-frame-shadow&#039;, [width - 1, d.ROW_HEIGHT - 1], [1, d.ROW_HEIGHT - 1]));&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-shadow&#039;,&lt;br /&gt;
            [frame.FRAME_FACE, frame.FRAME_FACE],&lt;br /&gt;
            [width - frame.FRAME_FACE, frame.FRAME_FACE]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-shadow&#039;,&lt;br /&gt;
            [width - frame.FRAME_FACE, frame.FRAME_FACE],&lt;br /&gt;
            [width - frame.FRAME_FACE, d.ROW_HEIGHT - frame.FRAME_FACE]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-highlight&#039;,&lt;br /&gt;
            [frame.FRAME_FACE, d.ROW_HEIGHT - frame.FRAME_FACE],&lt;br /&gt;
            [frame.FRAME_FACE, frame.FRAME_FACE]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-highlight&#039;,&lt;br /&gt;
            [width - frame.FRAME_FACE, d.ROW_HEIGHT - frame.FRAME_FACE],&lt;br /&gt;
            [frame.FRAME_FACE, d.ROW_HEIGHT - frame.FRAME_FACE]&lt;br /&gt;
        ));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createSegment(item, index, geometry) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var frame = api.FRAME;&lt;br /&gt;
        var data = geometry.segments[index];&lt;br /&gt;
        var d = geometry.dimensions;&lt;br /&gt;
        var anchor = api.create(&#039;a&#039;, &#039;portal-cat-anchor&#039;);&lt;br /&gt;
        var outer = api.polygon(&#039;portal-cat-edge&#039;, data.points);&lt;br /&gt;
        var surfacePoints = api.segmentAt(&lt;br /&gt;
            data.leftTopX,&lt;br /&gt;
            data.rightTopX,&lt;br /&gt;
            d.BUTTON_TOP + frame.BORDER,&lt;br /&gt;
            d.BUTTON_BOTTOM - frame.BORDER,&lt;br /&gt;
            data.leftDir,&lt;br /&gt;
            data.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER,&lt;br /&gt;
            frame.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var bevelPoints = api.segmentAt(&lt;br /&gt;
            data.leftTopX,&lt;br /&gt;
            data.rightTopX,&lt;br /&gt;
            d.BUTTON_TOP + frame.BORDER + frame.BEVEL,&lt;br /&gt;
            d.BUTTON_BOTTOM - frame.BORDER - frame.BEVEL,&lt;br /&gt;
            data.leftDir,&lt;br /&gt;
            data.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER + frame.BEVEL,&lt;br /&gt;
            frame.BORDER + frame.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
        var hit = api.polygon(&#039;portal-cat-hit&#039;, data.points);&lt;br /&gt;
        var text = api.create(&#039;text&#039;, &#039;portal-cat-label&#039;);&lt;br /&gt;
        var href = buildPath(item.title);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, item.key);&lt;br /&gt;
&lt;br /&gt;
        anchor.appendChild(outer);&lt;br /&gt;
        anchor.appendChild(api.polygon(&#039;portal-cat-surface&#039;, surfacePoints));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-top&#039;, bevelPoints[0], bevelPoints[1]));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-right&#039;, bevelPoints[1], bevelPoints[2]));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-bottom&#039;, bevelPoints[2], bevelPoints[3]));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-left&#039;, bevelPoints[3], bevelPoints[0]));&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
        anchor.appendChild(hit);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, api.centerX(surfacePoints));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, Math.round((d.BUTTON_TOP + d.BUTTON_BOTTOM) / 2) + 1);&lt;br /&gt;
        text.setAttribute(&#039;font-size&#039;, &#039;12&#039;);&lt;br /&gt;
        text.setAttribute(&#039;font-weight&#039;, &#039;700&#039;);&lt;br /&gt;
        text.setAttribute(&#039;text-anchor&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;dominant-baseline&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        text.textContent = item.label;&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        return anchor;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function publishSeams(geometry) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var segments = geometry.segments;&lt;br /&gt;
        var d = geometry.dimensions;&lt;br /&gt;
&lt;br /&gt;
        api.setSeams({&lt;br /&gt;
            width:geometry.width,&lt;br /&gt;
            rowHeight:d.ROW_HEIGHT,&lt;br /&gt;
            buttonTop:d.BUTTON_TOP,&lt;br /&gt;
            buttonBottom:d.BUTTON_BOTTOM,&lt;br /&gt;
            overlap:d.OVERLAP,&lt;br /&gt;
            bottomStartAbsY:d.BOTTOM_START_ABS_Y,&lt;br /&gt;
            settingsLeftTopX:segments[1].leftTopX,&lt;br /&gt;
            settingsLeftDir:segments[1].leftDir,&lt;br /&gt;
            corpRightTopX:segments[3].rightTopX,&lt;br /&gt;
            corpRightDir:segments[3].rightDir,&lt;br /&gt;
            projectLeftTopX:segments[2].leftTopX,&lt;br /&gt;
            projectLeftDir:segments[2].leftDir,&lt;br /&gt;
            projectRightTopX:segments[2].rightTopX,&lt;br /&gt;
            projectRightDir:segments[2].rightDir&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildSvg(width) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var geometry = calculateGeometry(width);&lt;br /&gt;
        var svg = api.create(&#039;svg&#039;, &#039;portal-category-svg&#039;);&lt;br /&gt;
&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + geometry.width + &#039; &#039; + geometry.dimensions.ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, geometry.dimensions.ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;none&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;aria-hidden&#039;, &#039;false&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;data-frame-width&#039;, geometry.width);&lt;br /&gt;
&lt;br /&gt;
        appendFrame(svg, geometry);&lt;br /&gt;
        ITEMS.forEach(function (item, index) {&lt;br /&gt;
            svg.appendChild(createSegment(item, index, geometry));&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return { svg:svg, geometry:geometry };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderMount(mount) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var width;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var result;&lt;br /&gt;
&lt;br /&gt;
        if (!mount || !api) return false;&lt;br /&gt;
        width = api.readWidth(mount);&lt;br /&gt;
        if (!width) {&lt;br /&gt;
            if (!mount.__categoryNavRetry) {&lt;br /&gt;
                mount.__categoryNavRetry = window.setTimeout(function () {&lt;br /&gt;
                    mount.__categoryNavRetry = 0;&lt;br /&gt;
                    renderMount(mount);&lt;br /&gt;
                }, 60);&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (mount.__categoryNavRetry) {&lt;br /&gt;
            window.clearTimeout(mount.__categoryNavRetry);&lt;br /&gt;
            mount.__categoryNavRetry = 0;&lt;br /&gt;
        }&lt;br /&gt;
        nav = mount.querySelector(&#039;.portal-category-nav&#039;);&lt;br /&gt;
        if (!nav) {&lt;br /&gt;
            clearNode(mount);&lt;br /&gt;
            nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
            nav.className = &#039;portal-category-nav&#039;;&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, &#039;Main categories&#039;);&lt;br /&gt;
            mount.appendChild(nav);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (nav.__frameWidth === width &amp;amp;&amp;amp; nav.querySelector(&#039;.portal-category-svg&#039;)) {&lt;br /&gt;
            publishSeams(calculateGeometry(width));&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        result = buildSvg(width);&lt;br /&gt;
        nav.__frameWidth = width;&lt;br /&gt;
        clearNode(nav);&lt;br /&gt;
        nav.appendChild(result.svg);&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        publishSeams(result.geometry);&lt;br /&gt;
&lt;br /&gt;
        if (window.ResizeObserver &amp;amp;&amp;amp; !mount.__categoryNavObserver) {&lt;br /&gt;
            mount.__categoryNavObserver = new window.ResizeObserver(function () {&lt;br /&gt;
                schedule(mount);&lt;br /&gt;
            });&lt;br /&gt;
            mount.__categoryNavObserver.observe(mount);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var mounts = [];&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;)) mounts.push(scope);&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;), function (mount) {&lt;br /&gt;
            if (mounts.indexOf(mount) === -1) mounts.push(mount);&lt;br /&gt;
        });&lt;br /&gt;
        mounts.forEach(function (mount) {&lt;br /&gt;
            if (renderMount(mount)) rendered += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { renderAll(document); });&lt;br /&gt;
    } else {&lt;br /&gt;
        renderAll(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            schedule(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () { schedule(document); });&lt;br /&gt;
&lt;br /&gt;
    window.CategoryNav = {&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderAll:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
    window.CLBI = window.CLBI || {};&lt;br /&gt;
    window.CLBI.categoryNav = {&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4667</id>
		<title>미디어위키:BottomGuideNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4667"/>
		<updated>2026-07-17T02:35:52Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-display-fix-20260717 / js/BottomGuideNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
BottomGuideNav&lt;br /&gt;
대문 하단 「길라잡이」 SVG 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
상단 CategoryNav와 분리해 로드한다.&lt;br /&gt;
FrameGeometry.js가 공유한 설정–프로젝트–기업 및 공동체 seam을 사용하며,&lt;br /&gt;
상단 SVG나 화면 rect를 다시 읽지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var COMPONENT = &#039;category-guide-nav&#039;;&lt;br /&gt;
    var FRAME_API = window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    var HEIGHT = FRAME_API.FRAME.BUTTON_FACE;&lt;br /&gt;
    var TOP_FRAME = FRAME_API.FRAME.FRAME_FACE;&lt;br /&gt;
    var BOTTOM_FRAME = FRAME_API.FRAME.FRAME_FACE;&lt;br /&gt;
    var GAP = FRAME_API.FRAME.GAP;&lt;br /&gt;
    var ROW_HEIGHT = TOP_FRAME + GAP + HEIGHT + GAP + BOTTOM_FRAME;&lt;br /&gt;
    var BUTTON_TOP = TOP_FRAME + GAP;&lt;br /&gt;
    var BUTTON_BOTTOM = BUTTON_TOP + HEIGHT;&lt;br /&gt;
    var COMPACT_HEIGHT = HEIGHT;&lt;br /&gt;
    var COMPACT_TOP_FRAME = TOP_FRAME;&lt;br /&gt;
    var COMPACT_BOTTOM_FRAME = BOTTOM_FRAME;&lt;br /&gt;
    var retryTimer = 0;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var guideLayoutAnimationFrame = 0;&lt;br /&gt;
    var observer = null;&lt;br /&gt;
    var manifestoIntroPromise = null;&lt;br /&gt;
    var MANIFESTO_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var MANIFESTO_INTRO_VERSION = &#039;20260713-divider-equal-gap-005&#039;;&lt;br /&gt;
    var GUIDE_DUMMY_ITEMS = [&lt;br /&gt;
        { key: &#039;start&#039;, label: &#039;처음 오셨다면&#039; },&lt;br /&gt;
        { key: &#039;world&#039;, label: &#039;세계관&#039; },&lt;br /&gt;
        { key: &#039;era&#039;, label: &#039;시대&#039; },&lt;br /&gt;
        { key: &#039;people&#039;, label: &#039;인물&#039; },&lt;br /&gt;
        { key: &#039;anecdote&#039;, label: &#039;에넥도트&#039; },&lt;br /&gt;
        { key: &#039;participate&#039;, label: &#039;참여 안내&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function revealManifesto(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        var portal = scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;) || document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var copy;&lt;br /&gt;
&lt;br /&gt;
        /* 이전 캐시에서 남은 전역 잠금을 즉시 해제한다. */&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-pending&#039;);&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-active&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-prepared&#039;);&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-running&#039;);&lt;br /&gt;
        title = portal.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        copy = portal.querySelector(&#039;.main-manifesto-copy&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (title) {&lt;br /&gt;
            title.style.opacity = &#039;1&#039;;&lt;br /&gt;
            title.style.borderBottom = &#039;0&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (copy) copy.style.visibility = &#039;visible&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line&#039;), function (line) {&lt;br /&gt;
            line.style.height = &#039;26px&#039;;&lt;br /&gt;
            line.style.maxHeight = &#039;26px&#039;;&lt;br /&gt;
            line.style.opacity = &#039;1&#039;;&lt;br /&gt;
            line.style.overflow = &#039;visible&#039;;&lt;br /&gt;
            line.style.clipPath = &#039;inset(0 0 0 0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line-inner&#039;), function (inner) {&lt;br /&gt;
            inner.style.opacity = &#039;1&#039;;&lt;br /&gt;
            inner.style.transform = &#039;translateY(0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifestoIntro(root) {&lt;br /&gt;
        if (&lt;br /&gt;
            window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
            window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
            typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(window.ManifestoIntro.init(root || document)).catch(function () {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    return false;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                revealManifesto(root);&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifestoIntroPromise) return manifestoIntroPromise;&lt;br /&gt;
&lt;br /&gt;
        manifestoIntroPromise = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                if (!ok) {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    manifestoIntroPromise = null;&lt;br /&gt;
                }&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            script.src = &#039;/index.php?title=&#039; + encodeURIComponent(&#039;MediaWiki:ManifestoIntro.js&#039;) +&lt;br /&gt;
                &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&amp;amp;v=&#039; + encodeURIComponent(MANIFESTO_INTRO_VERSION) +&lt;br /&gt;
                &#039;&amp;amp;_manifestoFresh=&#039; + encodeURIComponent(String(Date.now()));&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-manifesto-intro-script&#039;, &#039;1&#039;);&lt;br /&gt;
            script.onload = function () {&lt;br /&gt;
                if (&lt;br /&gt;
                    window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
                    window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
                    typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    Promise.resolve(window.ManifestoIntro.init(root || document))&lt;br /&gt;
                        .then(function () { settle(true); })&lt;br /&gt;
                        .catch(function () { settle(false); });&lt;br /&gt;
                } else {&lt;br /&gt;
                    settle(false);&lt;br /&gt;
                }&lt;br /&gt;
            };&lt;br /&gt;
            script.onerror = function () {&lt;br /&gt;
                settle(false);&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, MANIFESTO_LOAD_TIMEOUT_MS);&lt;br /&gt;
&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return manifestoIntroPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    revealManifesto(document);&lt;br /&gt;
&lt;br /&gt;
    function createSvgElement(name) {&lt;br /&gt;
        return FRAME_API.create(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildWikiPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) {&lt;br /&gt;
            return window.buildWikiPath(title);&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPolygon(className, points) {&lt;br /&gt;
        return FRAME_API.polygon(className, points);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createLine(className, start, end) {&lt;br /&gt;
        return FRAME_API.line(className, start, end);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createRect(className, x, y, width, height) {&lt;br /&gt;
        return FRAME_API.rect(className, x, y, width, height);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function centerOfPoints(points) {&lt;br /&gt;
        return [FRAME_API.centerX(points), Math.round((points[0][1] + points[2][1]) / 2)];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function shiftY(points, amount) {&lt;br /&gt;
        return points.map(function (point) {&lt;br /&gt;
            return [Math.round(point[0]), Math.round(point[1] + amount)];&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeGuideGeometry(height, topFrame, bottomFrame, sideFrameProgress) {&lt;br /&gt;
        height = Math.round(height);&lt;br /&gt;
        topFrame = Math.round(topFrame);&lt;br /&gt;
        bottomFrame = Math.round(bottomFrame);&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            height: height,&lt;br /&gt;
            topFrame: topFrame,&lt;br /&gt;
            bottomFrame: bottomFrame,&lt;br /&gt;
            buttonTop: topFrame + GAP,&lt;br /&gt;
            buttonBottom: topFrame + GAP + height,&lt;br /&gt;
            rowHeight: topFrame + GAP + height + GAP + bottomFrame,&lt;br /&gt;
            sideFrameProgress: Math.max(0, Math.min(1, Number(sideFrameProgress || 0)))&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getGuideGeometry(compact) {&lt;br /&gt;
        return compact&lt;br /&gt;
            ? makeGuideGeometry(COMPACT_HEIGHT, COMPACT_TOP_FRAME, COMPACT_BOTTOM_FRAME, 1)&lt;br /&gt;
            : makeGuideGeometry(HEIGHT, TOP_FRAME, BOTTOM_FRAME, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function interpolateGuideGeometry(from, to, progress) {&lt;br /&gt;
        function between(start, end) {&lt;br /&gt;
            return start + (end - start) * progress;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return makeGuideGeometry(&lt;br /&gt;
            between(from.height, to.height),&lt;br /&gt;
            between(from.topFrame, to.topFrame),&lt;br /&gt;
            between(from.bottomFrame, to.bottomFrame),&lt;br /&gt;
            between(from.sideFrameProgress, to.sideFrameProgress)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyGuideRowHeight(mount, nav, svg, geometry) {&lt;br /&gt;
        var height = geometry.rowHeight + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        [mount, nav, svg].forEach(function (node) {&lt;br /&gt;
            if (!node || !node.style) return;&lt;br /&gt;
            node.style.setProperty(&#039;height&#039;, height, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;min-height&#039;, height, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;max-height&#039;, height, &#039;important&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestGuideFrame(callback) {&lt;br /&gt;
        if (typeof window.requestAnimationFrame === &#039;function&#039;) {&lt;br /&gt;
            return window.requestAnimationFrame(callback);&lt;br /&gt;
        }&lt;br /&gt;
        return window.setTimeout(function () { callback(Date.now()); }, 16);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function cancelGuideFrame(handle) {&lt;br /&gt;
        if (!handle) return;&lt;br /&gt;
        if (typeof window.cancelAnimationFrame === &#039;function&#039;) {&lt;br /&gt;
            window.cancelAnimationFrame(handle);&lt;br /&gt;
        } else {&lt;br /&gt;
            window.clearTimeout(handle);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function animateGuideLayout(portal, compact) {&lt;br /&gt;
        var from = portal &amp;amp;&amp;amp; portal.GuideNavGeometry&lt;br /&gt;
            ? portal.GuideNavGeometry&lt;br /&gt;
            : getGuideGeometry(!compact);&lt;br /&gt;
        var to = getGuideGeometry(compact);&lt;br /&gt;
        var startedAt = null;&lt;br /&gt;
        var duration = 340;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
        if (guideLayoutAnimationFrame) {&lt;br /&gt;
            cancelGuideFrame(guideLayoutAnimationFrame);&lt;br /&gt;
            guideLayoutAnimationFrame = 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (window.matchMedia &amp;amp;&amp;amp; window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            portal.GuideNavGeometry = to;&lt;br /&gt;
            render(portal, to);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function step(now) {&lt;br /&gt;
            var elapsed;&lt;br /&gt;
            var progress;&lt;br /&gt;
            var eased;&lt;br /&gt;
&lt;br /&gt;
            if (startedAt === null) startedAt = now;&lt;br /&gt;
            elapsed = Math.min(1, (now - startedAt) / duration);&lt;br /&gt;
            eased = 1 - Math.pow(1 - elapsed, 3);&lt;br /&gt;
            progress = interpolateGuideGeometry(from, to, eased);&lt;br /&gt;
            portal.GuideNavGeometry = progress;&lt;br /&gt;
            render(portal, progress);&lt;br /&gt;
&lt;br /&gt;
            if (elapsed &amp;lt; 1) {&lt;br /&gt;
                guideLayoutAnimationFrame = requestGuideFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.GuideNavGeometry = to;&lt;br /&gt;
                guideLayoutAnimationFrame = 0;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        guideLayoutAnimationFrame = requestGuideFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildGuideButton(geometry) {&lt;br /&gt;
        var seams = FRAME_API.seams;&lt;br /&gt;
        var leftTop;&lt;br /&gt;
        var rightTop;&lt;br /&gt;
        var leftDir;&lt;br /&gt;
        var rightDir;&lt;br /&gt;
&lt;br /&gt;
        if (!seams || !seams.width) return null;&lt;br /&gt;
        leftTop = FRAME_API.slantX(&lt;br /&gt;
            seams.settingsLeftTopX,&lt;br /&gt;
            seams.buttonBottom,&lt;br /&gt;
            seams.settingsLeftDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        rightTop = FRAME_API.slantX(&lt;br /&gt;
            seams.corpRightTopX,&lt;br /&gt;
            seams.buttonBottom,&lt;br /&gt;
            seams.corpRightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        leftDir = -seams.settingsLeftDir;&lt;br /&gt;
        rightDir = -seams.corpRightDir;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            leftTopX:leftTop,&lt;br /&gt;
            rightTopX:rightTop,&lt;br /&gt;
            leftDir:leftDir,&lt;br /&gt;
            rightDir:rightDir,&lt;br /&gt;
            points:shiftY(&lt;br /&gt;
                FRAME_API.segment(leftTop, rightTop, geometry.height, leftDir, rightDir, 0),&lt;br /&gt;
                geometry.buttonTop&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function guideLayer(button, geometry, amount) {&lt;br /&gt;
        return shiftY(&lt;br /&gt;
            FRAME_API.segment(&lt;br /&gt;
                button.leftTopX + amount,&lt;br /&gt;
                button.rightTopX - amount,&lt;br /&gt;
                geometry.height - amount * 2,&lt;br /&gt;
                button.leftDir,&lt;br /&gt;
                button.rightDir,&lt;br /&gt;
                amount % FRAME_API.FRAME.SLANT_RISE&lt;br /&gt;
            ),&lt;br /&gt;
            geometry.buttonTop + amount&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendContinuousBottomFrame(svg, button, width, geometry) {&lt;br /&gt;
        var frame = FRAME_API.FRAME;&lt;br /&gt;
        var group = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var leftTarget = button.points[0][0] - frame.FRAME_FACE - frame.GAP;&lt;br /&gt;
        var rightTarget = button.points[1][0] + frame.FRAME_FACE + frame.GAP;&lt;br /&gt;
        var leftBase = leftTarget - FRAME_API.slantX(0, geometry.buttonTop, button.leftDir, 0);&lt;br /&gt;
        var rightBase = rightTarget - FRAME_API.slantX(0, geometry.buttonTop, button.rightDir, 0);&lt;br /&gt;
        var outer = FRAME_API.segment(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.rowHeight,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        var well = FRAME_API.segmentAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.topFrame,&lt;br /&gt;
            geometry.rowHeight - geometry.bottomFrame,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.FRAME_FACE,&lt;br /&gt;
            frame.FRAME_FACE&lt;br /&gt;
        );&lt;br /&gt;
        var bevel = FRAME_API.segmentAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            frame.BORDER + frame.BEVEL,&lt;br /&gt;
            geometry.rowHeight - frame.BORDER - frame.BEVEL,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER + frame.BEVEL,&lt;br /&gt;
            frame.BORDER + frame.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        group.setAttribute(&#039;class&#039;, &#039;portal-guide-arch-frame&#039;);&lt;br /&gt;
        group.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-guide-arch-outer&#039;, outer));&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-guide-arch-well&#039;, well));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[3], outer[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[0], outer[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[1], outer[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-highlight&#039;, bevel[0], bevel[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-highlight&#039;, bevel[1], bevel[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-shadow&#039;, bevel[3], bevel[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-highlight&#039;, well[0], well[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-highlight&#039;, well[1], well[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-shadow&#039;, well[3], well[0]));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyPanel(portal) {&lt;br /&gt;
        var panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        var manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) {&lt;br /&gt;
                    topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                } else {&lt;br /&gt;
                    topMount.parentNode.appendChild(panel);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) {&lt;br /&gt;
            well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            panel: panel,&lt;br /&gt;
            well: well&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBodyScreenHidden(screen, hidden) {&lt;br /&gt;
        if (!screen) return;&lt;br /&gt;
&lt;br /&gt;
        screen.classList.toggle(&#039;is-active&#039;, !hidden);&lt;br /&gt;
        screen.hidden = hidden;&lt;br /&gt;
        screen.setAttribute(&#039;aria-hidden&#039;, hidden ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateGuideButtonState(portal, active) {&lt;br /&gt;
        var guideActive = active === &#039;guide&#039;;&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var target = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-anchor&#039;), function (anchor) {&lt;br /&gt;
            anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
            anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            anchor.setAttribute(&#039;data-main-body-target&#039;, target);&lt;br /&gt;
            anchor.removeAttribute(&#039;aria-current&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-label&#039;), function (text) {&lt;br /&gt;
            text.textContent = label;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-nav&#039;), function (nav) {&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureGuideDummyMenu(guide) {&lt;br /&gt;
        var menu;&lt;br /&gt;
&lt;br /&gt;
        if (!guide) return null;&lt;br /&gt;
&lt;br /&gt;
        menu = guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
        if (menu) return menu;&lt;br /&gt;
&lt;br /&gt;
        menu = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        menu.className = &#039;main-guide-menu&#039;;&lt;br /&gt;
        menu.setAttribute(&#039;data-guide-dummy-menu&#039;, &#039;1&#039;);&lt;br /&gt;
        menu.setAttribute(&#039;aria-label&#039;, &#039;길라잡이 항목 예시&#039;);&lt;br /&gt;
&lt;br /&gt;
        GUIDE_DUMMY_ITEMS.forEach(function (item, index) {&lt;br /&gt;
            var wrapper = document.createElement(&#039;div&#039;);&lt;br /&gt;
            var button = document.createElement(&#039;button&#039;);&lt;br /&gt;
            var number = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var arrow = document.createElement(&#039;span&#039;);&lt;br /&gt;
&lt;br /&gt;
            wrapper.className = &#039;main-guide-menu-item&#039;;&lt;br /&gt;
            wrapper.style.setProperty(&#039;--guide-menu-order&#039;, String(index));&lt;br /&gt;
&lt;br /&gt;
            button.type = &#039;button&#039;;&lt;br /&gt;
            button.className = &#039;main-guide-menu-button&#039;;&lt;br /&gt;
            button.setAttribute(&#039;data-guide-dummy-button&#039;, item.key);&lt;br /&gt;
            button.setAttribute(&#039;aria-label&#039;, item.label + &#039; (더미 버튼)&#039;);&lt;br /&gt;
&lt;br /&gt;
            number.className = &#039;main-guide-menu-number&#039;;&lt;br /&gt;
            number.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            number.textContent = String(index + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
            label.className = &#039;main-guide-menu-label&#039;;&lt;br /&gt;
            label.textContent = item.label;&lt;br /&gt;
&lt;br /&gt;
            arrow.className = &#039;main-guide-menu-arrow&#039;;&lt;br /&gt;
            arrow.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            arrow.textContent = &#039;›&#039;;&lt;br /&gt;
&lt;br /&gt;
            button.appendChild(number);&lt;br /&gt;
            button.appendChild(label);&lt;br /&gt;
            button.appendChild(arrow);&lt;br /&gt;
            button.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
            });&lt;br /&gt;
            wrapper.appendChild(button);&lt;br /&gt;
            menu.appendChild(wrapper);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        guide.insertBefore(menu, guide.firstChild);&lt;br /&gt;
        return menu;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function restartGuideMenuAnimation(guide) {&lt;br /&gt;
        if (!guide || !guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        /* 같은 화면에 재진입해도 첫 프레임부터 순차 진입을 다시 시작한다. */&lt;br /&gt;
        void guide.offsetWidth;&lt;br /&gt;
        guide.classList.add(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyScreens(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var manifesto = layout.well.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
        var guide = layout.well.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;);&lt;br /&gt;
        var panels;&lt;br /&gt;
        var active = layout.well.getAttribute(&#039;data-active-main-body-screen&#039;) ||&lt;br /&gt;
            portal.getAttribute(&#039;data-main-body-screen&#039;) ||&lt;br /&gt;
            &#039;manifesto&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (manifesto) {&lt;br /&gt;
            manifesto.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            manifesto.setAttribute(&#039;data-main-body-screen&#039;, &#039;manifesto&#039;);&lt;br /&gt;
            if (!manifesto.id) manifesto.id = &#039;main-manifesto-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!guide) {&lt;br /&gt;
            guide = document.createElement(&#039;section&#039;);&lt;br /&gt;
            guide.className = &#039;main-body-screen main-guide-screen&#039;;&lt;br /&gt;
            guide.setAttribute(&#039;data-main-body-screen&#039;, &#039;guide&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;data-main-guide-screen-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;aria-label&#039;, &#039;길라잡이&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;role&#039;, &#039;region&#039;);&lt;br /&gt;
            guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
            layout.well.appendChild(guide);&lt;br /&gt;
        } else {&lt;br /&gt;
            guide.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            guide.classList.add(&#039;main-guide-screen&#039;);&lt;br /&gt;
            if (!guide.id) guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        ensureGuideDummyMenu(guide);&lt;br /&gt;
&lt;br /&gt;
        panels = Array.prototype.slice.call(&lt;br /&gt;
            layout.well.querySelectorAll(&#039;.main-body-screen[data-main-body-screen]&#039;)&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (active !== &#039;guide&#039;) active = &#039;manifesto&#039;;&lt;br /&gt;
        if (!panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === active;&lt;br /&gt;
        })) {&lt;br /&gt;
            active = &#039;manifesto&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(&lt;br /&gt;
                panel,&lt;br /&gt;
                panel.getAttribute(&#039;data-main-body-screen&#039;) !== active&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        layout.well.setAttribute(&#039;data-main-body-screen-controller-ready&#039;, &#039;1&#039;);&lt;br /&gt;
        layout.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; !guide.classList.contains(&#039;is-guide-menu-entering&#039;)) {&lt;br /&gt;
            restartGuideMenuAnimation(guide);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            well: layout.well,&lt;br /&gt;
            panels: panels,&lt;br /&gt;
            manifesto: manifesto,&lt;br /&gt;
            guide: guide,&lt;br /&gt;
            active: active&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBodyScreen(portal, screenName) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var previous;&lt;br /&gt;
        var active;&lt;br /&gt;
        var event;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        screenName = screenName === &#039;guide&#039; ? &#039;guide&#039; : &#039;manifesto&#039;;&lt;br /&gt;
        if (!screens.panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === screenName;&lt;br /&gt;
        })) return false;&lt;br /&gt;
&lt;br /&gt;
        previous = screens.active;&lt;br /&gt;
        active = screenName;&lt;br /&gt;
&lt;br /&gt;
        screens.panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(&lt;br /&gt;
                panel,&lt;br /&gt;
                panel.getAttribute(&#039;data-main-body-screen&#039;) !== active&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        screens.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; previous !== active) {&lt;br /&gt;
            restartGuideMenuAnimation(screens.guide);&lt;br /&gt;
        } else if (active !== &#039;guide&#039; &amp;amp;&amp;amp; screens.guide) {&lt;br /&gt;
            screens.guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            animateGuideLayout(portal, active === &#039;guide&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            try {&lt;br /&gt;
                event = new CustomEvent(&#039;mainbodyscreenchange&#039;, {&lt;br /&gt;
                    bubbles: true,&lt;br /&gt;
                    detail: { screen: active, previous: previous }&lt;br /&gt;
                });&lt;br /&gt;
                portal.dispatchEvent(event);&lt;br /&gt;
            } catch (error) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function toggleBodyScreen(portal) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        target = screens.active === &#039;guide&#039; ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        return activateBodyScreen(portal, target);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleGuideScreenCapture(event) {&lt;br /&gt;
        var target;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var portal;&lt;br /&gt;
&lt;br /&gt;
        if (!event || event.__mainGuideScreenHandled) return;&lt;br /&gt;
        if (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        target = event.target;&lt;br /&gt;
        anchor = target &amp;amp;&amp;amp; target.closest&lt;br /&gt;
            ? target.closest(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)&lt;br /&gt;
            : null;&lt;br /&gt;
        if (!anchor) return;&lt;br /&gt;
&lt;br /&gt;
        portal = anchor.closest ? anchor.closest(&#039;.main-portal&#039;) : null;&lt;br /&gt;
        if (!portal || !toggleBodyScreen(portal)) return;&lt;br /&gt;
&lt;br /&gt;
        event.__mainGuideScreenHandled = true;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) {&lt;br /&gt;
            event.stopImmediatePropagation();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    이전 실행본의 ResizeObserver가 오래된 문서 링크를 다시 그려도&lt;br /&gt;
    전역 SPA 링크 처리보다 먼저 화면 전환으로 고정한다.&lt;br /&gt;
    */&lt;br /&gt;
    if (window.BottomGuideNavScreenCaptureHandler) {&lt;br /&gt;
        document.removeEventListener(&#039;click&#039;, window.BottomGuideNavScreenCaptureHandler, true);&lt;br /&gt;
    }&lt;br /&gt;
    window.BottomGuideNavScreenCaptureHandler = handleGuideScreenCapture;&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleGuideScreenCapture, true);&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        ensureBodyScreens(portal);&lt;br /&gt;
&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
            mount.setAttribute(&#039;data-bottom-guide-generated&#039;, &#039;1&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        길라잡이는 우물 안의 독립 버튼이 아니라&lt;br /&gt;
        main-body-panel 하단 프레임에 융합된 돌출부다.&lt;br /&gt;
        */&lt;br /&gt;
        if (mount.parentNode !== layout.panel) {&lt;br /&gt;
            layout.panel.appendChild(mount);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(portal, suppliedGeometry) {&lt;br /&gt;
        var mount = ensureMount(portal);&lt;br /&gt;
        var width = FRAME_API.readWidth(mount);&lt;br /&gt;
        var guideActive = portal.getAttribute(&#039;data-main-body-screen&#039;) === &#039;guide&#039;;&lt;br /&gt;
        var geometry = suppliedGeometry || portal.GuideNavGeometry || getGuideGeometry(guideActive);&lt;br /&gt;
&lt;br /&gt;
        if (!width) return false;&lt;br /&gt;
&lt;br /&gt;
        portal.GuideNavGeometry = geometry;&lt;br /&gt;
&lt;br /&gt;
        var button = buildGuideButton(geometry);&lt;br /&gt;
        var points;&lt;br /&gt;
        var surfacePoints;&lt;br /&gt;
        var insetPoints;&lt;br /&gt;
        var center;&lt;br /&gt;
&lt;br /&gt;
        if (!button) return false;&lt;br /&gt;
        points = button.points;&lt;br /&gt;
        surfacePoints = guideLayer(button, geometry, FRAME_API.FRAME.BORDER);&lt;br /&gt;
        insetPoints = guideLayer(&lt;br /&gt;
            button,&lt;br /&gt;
            geometry,&lt;br /&gt;
            FRAME_API.FRAME.BORDER + FRAME_API.FRAME.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
        center = centerOfPoints(surfacePoints);&lt;br /&gt;
        var nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        var svg = createSvgElement(&#039;svg&#039;);&lt;br /&gt;
        var anchor = createSvgElement(&#039;a&#039;);&lt;br /&gt;
        var buttonGeometry = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var targetScreen = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
        var text = createSvgElement(&#039;text&#039;);&lt;br /&gt;
        var outer = createPolygon(&#039;portal-cat-edge&#039;, points);&lt;br /&gt;
        var surface = createPolygon(&#039;portal-cat-surface&#039;, surfacePoints);&lt;br /&gt;
        var hit = createPolygon(&#039;portal-cat-hit&#039;, points);&lt;br /&gt;
&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-guide-nav is-continuous-weld&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
&lt;br /&gt;
        svg.setAttribute(&#039;class&#039;, &#039;portal-category-svg portal-guide-svg&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + width + &#039; &#039; + geometry.rowHeight);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, geometry.rowHeight);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        appendContinuousBottomFrame(svg, button, width, geometry);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;class&#039;, &#039;portal-cat-anchor portal-guide-anchor&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        anchor.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, &#039;guide&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-main-body-target&#039;, targetScreen);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-controls&#039;, &#039;main-manifesto-screen main-guide-screen&#039;);&lt;br /&gt;
        buttonGeometry.setAttribute(&#039;class&#039;, &#039;portal-guide-button-geometry&#039;);&lt;br /&gt;
&lt;br /&gt;
        anchor.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            if (&lt;br /&gt;
                event &amp;amp;&amp;amp;&lt;br /&gt;
                (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (toggleBodyScreen(portal)) {&lt;br /&gt;
                if (event) event.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        outer.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        surface.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;class&#039;, &#039;portal-cat-label portal-guide-label&#039;);&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, Math.round(center[0]));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, Math.round((geometry.buttonTop + geometry.buttonBottom) / 2) + 1);&lt;br /&gt;
        text.setAttribute(&#039;font-size&#039;, &#039;12&#039;);&lt;br /&gt;
        text.setAttribute(&#039;font-weight&#039;, &#039;700&#039;);&lt;br /&gt;
        text.setAttribute(&#039;text-anchor&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;dominant-baseline&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        text.textContent = label;&lt;br /&gt;
&lt;br /&gt;
        buttonGeometry.appendChild(outer);&lt;br /&gt;
        buttonGeometry.appendChild(surface);&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-top&#039;,&lt;br /&gt;
            insetPoints[0],&lt;br /&gt;
            insetPoints[1]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-right&#039;,&lt;br /&gt;
            insetPoints[1],&lt;br /&gt;
            insetPoints[2]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-bottom&#039;,&lt;br /&gt;
            insetPoints[2],&lt;br /&gt;
            insetPoints[3]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-left&#039;,&lt;br /&gt;
            insetPoints[3],&lt;br /&gt;
            insetPoints[0]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(hit);&lt;br /&gt;
        anchor.appendChild(buttonGeometry);&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        svg.appendChild(anchor);&lt;br /&gt;
        nav.appendChild(svg);&lt;br /&gt;
&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        applyGuideRowHeight(mount, nav, svg, geometry);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-width&#039;, String(width));&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findPortal(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, attempt) {&lt;br /&gt;
        window.clearTimeout(retryTimer);&lt;br /&gt;
&lt;br /&gt;
        retryTimer = window.setTimeout(function () {&lt;br /&gt;
            var portal = findPortal(root);&lt;br /&gt;
&lt;br /&gt;
            if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
            if (!render(portal) &amp;amp;&amp;amp; attempt &amp;lt; 20) {&lt;br /&gt;
                schedule(root, attempt + 1);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (observer) observer.disconnect();&lt;br /&gt;
&lt;br /&gt;
            if (window.ResizeObserver) {&lt;br /&gt;
                observer = new ResizeObserver(function () {&lt;br /&gt;
                    window.clearTimeout(resizeTimer);&lt;br /&gt;
                    resizeTimer = window.setTimeout(function () {&lt;br /&gt;
                        render(portal);&lt;br /&gt;
                    }, 60);&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                observer.observe(portal);&lt;br /&gt;
                var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
                var guideMount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
                if (topMount) observer.observe(topMount);&lt;br /&gt;
                if (guideMount) observer.observe(guideMount);&lt;br /&gt;
            }&lt;br /&gt;
        }, attempt ? 60 : 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot(root) {&lt;br /&gt;
        var scope = root || document;&lt;br /&gt;
        schedule(scope, 0);&lt;br /&gt;
        loadManifestoIntro(scope);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {&lt;br /&gt;
            boot(document);&lt;br /&gt;
        }, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* 일반 링크 진입과 새로고침의 최종 로드 시점도 명시적으로 보장한다. */&lt;br /&gt;
    window.addEventListener(&#039;load&#039;, function () {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }, { once: true });&lt;br /&gt;
&lt;br /&gt;
    /* bfcache 복원과 브라우저 뒤로/앞으로 이동에서도 새 DOM만 초기화한다. */&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, function () {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            boot($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BottomGuideNav = {&lt;br /&gt;
        version: &#039;20260717-shared-frame-reverse-slant-001&#039;,&lt;br /&gt;
        render: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? render(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        activate: function (screenName) {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? activateBodyScreen(portal, screenName) : false;&lt;br /&gt;
        },&lt;br /&gt;
        toggle: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? toggleBodyScreen(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        status: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            var mount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
            return {&lt;br /&gt;
                loaded: true,&lt;br /&gt;
                portal: !!portal,&lt;br /&gt;
                mount: !!mount,&lt;br /&gt;
                rendered: !!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-svg&#039;)),&lt;br /&gt;
                generated: !!(mount &amp;amp;&amp;amp; mount.getAttribute(&#039;data-bottom-guide-generated&#039;)),&lt;br /&gt;
                panel: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;)),&lt;br /&gt;
                well: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-well&#039;)),&lt;br /&gt;
                insidePanel: !!(mount &amp;amp;&amp;amp; mount.parentNode &amp;amp;&amp;amp; mount.parentNode.classList.contains(&#039;main-body-panel&#039;)),&lt;br /&gt;
                welded: !!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-nav.is-continuous-weld&#039;)),&lt;br /&gt;
                activeScreen: portal ? portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039; : &#039;&#039;,&lt;br /&gt;
                guideScreen: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;)),&lt;br /&gt;
                width: mount ? mount.getAttribute(&#039;data-bottom-guide-width&#039;) || &#039;&#039; : &#039;&#039;&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4666</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4666"/>
		<updated>2026-07-17T02:35:51Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-portal-frame-display-fix-20260717 / css/MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:36px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
FrameGeometry.js가 공유한 설정 왼쪽 seam과 기업 및 공동체 오른쪽 seam을&lt;br /&gt;
같은 정수 좌표계에서 연장한다. 상단 프레임 하단 9px 안으로 겹쳐 접합하며,&lt;br /&gt;
표시 높이는 공통 ROW_HEIGHT 38px만 사용한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-outline {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:10%;&lt;br /&gt;
    right:auto;&lt;br /&gt;
    width:80%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 FrameGeometry.js의 공통 7 / 2 / 20 / 2 / 7 단면으로 38px이다. */&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단·하단 총 38px:&lt;br /&gt;
7px 프레임 + 2px #080808 + 20px 버튼 + 2px #080808 + 7px 프레임&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
사선 프레임 단일 정수 좌표계 / 20260717&lt;br /&gt;
FrameGeometry.js의 7 / 2 / 20 / 2 / 7 단면을 그대로 표시한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 38px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav,&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-highlight,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:focus .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 38px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame,&lt;br /&gt;
.main-portal .portal-section-frame,&lt;br /&gt;
.main-portal .portal-category-pillar,&lt;br /&gt;
.main-portal .portal-section-button-geometry,&lt;br /&gt;
.main-portal .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-hit {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 길라잡이도 같은 7 / 2 / 20 / 2 / 7 단면과 반대 사선을 사용한다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    stroke:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* main-body-panel의 1px 좌우 외곽선을 보정해 상단 seam 좌표폭과 일치시킨다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    left:-1px !important;&lt;br /&gt;
    right:-1px !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryPillar.js&amp;diff=4665</id>
		<title>미디어위키:CategoryPillar.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryPillar.js&amp;diff=4665"/>
		<updated>2026-07-17T02:15:17Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-frame-geometry-normalized-20260717 / js/CategoryPillar.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
CategoryPillar&lt;br /&gt;
대문 상단 카테고리의 중앙 「프로젝트」 기둥&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var VERSION = &#039;20260717-shared-frame-geometry-001&#039;;&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
&lt;br /&gt;
    function api() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSvg(svg, seams) {&lt;br /&gt;
        var frame = api();&lt;br /&gt;
        var source;&lt;br /&gt;
        var previous;&lt;br /&gt;
        var points;&lt;br /&gt;
        var group;&lt;br /&gt;
        var label;&lt;br /&gt;
        var centerY;&lt;br /&gt;
        var leftCenter;&lt;br /&gt;
        var rightCenter;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !svg || !seams || Number(svg.getAttribute(&#039;data-frame-width&#039;)) !== seams.width) return false;&lt;br /&gt;
        source = svg.querySelector(&#039;.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;]&#039;);&lt;br /&gt;
        if (!source) return false;&lt;br /&gt;
&lt;br /&gt;
        previous = svg.querySelector(&#039;.portal-category-pillar&#039;);&lt;br /&gt;
        if (previous) previous.remove();&lt;br /&gt;
&lt;br /&gt;
        points = frame.segmentAt(&lt;br /&gt;
            seams.projectLeftTopX,&lt;br /&gt;
            seams.projectRightTopX,&lt;br /&gt;
            0,&lt;br /&gt;
            seams.rowHeight,&lt;br /&gt;
            seams.projectLeftDir,&lt;br /&gt;
            seams.projectRightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            0,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        group = frame.create(&#039;g&#039;, &#039;portal-category-pillar&#039;);&lt;br /&gt;
        group.setAttribute(&#039;data-category-key&#039;, &#039;project&#039;);&lt;br /&gt;
        group.setAttribute(&#039;aria-label&#039;, &#039;프로젝트&#039;);&lt;br /&gt;
        group.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        group.appendChild(frame.polygon(&#039;portal-category-pillar-surface&#039;, points));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-category-pillar-line portal-category-pillar-shadow&#039;, points[3], points[0]));&lt;br /&gt;
        group.appendChild(frame.line(&#039;portal-category-pillar-line portal-category-pillar-highlight&#039;, points[1], points[2]));&lt;br /&gt;
&lt;br /&gt;
        centerY = Math.round((seams.buttonTop + seams.buttonBottom) / 2);&lt;br /&gt;
        leftCenter = frame.slantX(seams.projectLeftTopX, centerY, seams.projectLeftDir, 0);&lt;br /&gt;
        rightCenter = frame.slantX(seams.projectRightTopX, centerY, seams.projectRightDir, 0);&lt;br /&gt;
        label = frame.create(&#039;text&#039;, &#039;portal-cat-label portal-category-pillar-label&#039;);&lt;br /&gt;
        label.setAttribute(&#039;x&#039;, Math.round((leftCenter + rightCenter) / 2));&lt;br /&gt;
        label.setAttribute(&#039;y&#039;, centerY + 1);&lt;br /&gt;
        label.setAttribute(&#039;font-size&#039;, &#039;12&#039;);&lt;br /&gt;
        label.setAttribute(&#039;font-weight&#039;, &#039;700&#039;);&lt;br /&gt;
        label.setAttribute(&#039;text-anchor&#039;, &#039;middle&#039;);&lt;br /&gt;
        label.setAttribute(&#039;dominant-baseline&#039;, &#039;middle&#039;);&lt;br /&gt;
        label.textContent = &#039;프로젝트&#039;;&lt;br /&gt;
        group.appendChild(label);&lt;br /&gt;
&lt;br /&gt;
        source.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        source.setAttribute(&#039;tabindex&#039;, &#039;-1&#039;);&lt;br /&gt;
        source.classList.add(&#039;portal-category-pillar-source&#039;);&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
        svg.classList.add(&#039;has-category-pillar&#039;);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root, seams) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var current = seams || (api() &amp;amp;&amp;amp; api().seams);&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
&lt;br /&gt;
        if (!current || !current.width) return 0;&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            scope.querySelectorAll(&#039;.main-portal [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg&#039;),&lt;br /&gt;
            function (svg) {&lt;br /&gt;
                if (renderSvg(svg, current)) rendered += 1;&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, seams) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document, seams);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (api()) api().onSeams(function (seams) { schedule(document, seams); });&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            schedule(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.CategoryPillar = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        render:renderSvg,&lt;br /&gt;
        renderAll:renderAll&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4664</id>
		<title>미디어위키:CategoryNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:CategoryNav.js&amp;diff=4664"/>
		<updated>2026-07-17T02:15:17Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-frame-geometry-normalized-20260717 / js/CategoryNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - CategoryNav&lt;br /&gt;
대문 카테고리 SVG 네비 생성기&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var ITEMS = [&lt;br /&gt;
        { key:&#039;era&#039;, label:&#039;시대&#039;, title:&#039;시대&#039; },&lt;br /&gt;
        { key:&#039;settings&#039;, label:&#039;설정&#039;, title:&#039;설정&#039; },&lt;br /&gt;
        { key:&#039;project&#039;, label:&#039;프로젝트&#039;, title:&#039;프로젝트:소개&#039; },&lt;br /&gt;
        { key:&#039;corporations&#039;, label:&#039;기업 및 공동체&#039;, title:&#039;기업_및_공동체&#039; },&lt;br /&gt;
        { key:&#039;military&#039;, label:&#039;군, 정치집단&#039;, title:&#039;군_정치집단&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
    var BOUNDARY_DIRS = [0, 1, 1, -1, -1, 0];&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
&lt;br /&gt;
    function geometryApi() {&lt;br /&gt;
        return window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) return window.buildWikiPath(title);&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearNode(node) {&lt;br /&gt;
        while (node &amp;amp;&amp;amp; node.firstChild) node.removeChild(node.firstChild);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateGeometry(width) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var frame = api.FRAME;&lt;br /&gt;
        var dimensions = api.DIMENSIONS;&lt;br /&gt;
        var contentLeft = frame.FRAME_FACE;&lt;br /&gt;
        var contentWidth = Math.max(ITEMS.length, width - frame.FRAME_FACE * 2);&lt;br /&gt;
        var boundaries = [];&lt;br /&gt;
        var segments = [];&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt;= ITEMS.length; i++) {&lt;br /&gt;
            boundaries.push(contentLeft + Math.round(contentWidth * i / ITEMS.length));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; ITEMS.length; i++) {&lt;br /&gt;
            var leftTopX = boundaries[i] + (i === 0 ? 0 : frame.GAP / 2);&lt;br /&gt;
            var rightTopX = boundaries[i + 1] - (i === ITEMS.length - 1 ? 0 : frame.GAP / 2);&lt;br /&gt;
            segments.push({&lt;br /&gt;
                leftTopX:leftTopX,&lt;br /&gt;
                rightTopX:rightTopX,&lt;br /&gt;
                leftDir:BOUNDARY_DIRS[i],&lt;br /&gt;
                rightDir:BOUNDARY_DIRS[i + 1],&lt;br /&gt;
                points:api.segmentAt(&lt;br /&gt;
                    leftTopX,&lt;br /&gt;
                    rightTopX,&lt;br /&gt;
                    dimensions.BUTTON_TOP,&lt;br /&gt;
                    dimensions.BUTTON_BOTTOM,&lt;br /&gt;
                    BOUNDARY_DIRS[i],&lt;br /&gt;
                    BOUNDARY_DIRS[i + 1],&lt;br /&gt;
                    0,&lt;br /&gt;
                    0,&lt;br /&gt;
                    0&lt;br /&gt;
                )&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            width:width,&lt;br /&gt;
            boundaries:boundaries,&lt;br /&gt;
            segments:segments,&lt;br /&gt;
            dimensions:dimensions&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendFrame(svg, geometry) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var frame = api.FRAME;&lt;br /&gt;
        var d = geometry.dimensions;&lt;br /&gt;
        var width = geometry.width;&lt;br /&gt;
        var group = api.create(&#039;g&#039;, &#039;portal-category-frame&#039;);&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(api.rect(&#039;portal-category-frame-edge&#039;, 0, 0, width, d.ROW_HEIGHT));&lt;br /&gt;
        group.appendChild(api.rect(&#039;portal-category-frame-surface&#039;, 1, 1, width - 2, d.ROW_HEIGHT - 2));&lt;br /&gt;
        group.appendChild(api.rect(&lt;br /&gt;
            &#039;portal-category-frame-well&#039;,&lt;br /&gt;
            frame.FRAME_FACE,&lt;br /&gt;
            frame.FRAME_FACE,&lt;br /&gt;
            width - frame.FRAME_FACE * 2,&lt;br /&gt;
            d.ROW_HEIGHT - frame.FRAME_FACE * 2&lt;br /&gt;
        ));&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(api.line(&#039;portal-category-frame-highlight&#039;, [1, 1], [width - 1, 1]));&lt;br /&gt;
        group.appendChild(api.line(&#039;portal-category-frame-highlight&#039;, [width - 1, 1], [width - 1, d.ROW_HEIGHT - 1]));&lt;br /&gt;
        group.appendChild(api.line(&#039;portal-category-frame-shadow&#039;, [1, d.ROW_HEIGHT - 1], [1, 1]));&lt;br /&gt;
        group.appendChild(api.line(&#039;portal-category-frame-shadow&#039;, [width - 1, d.ROW_HEIGHT - 1], [1, d.ROW_HEIGHT - 1]));&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-shadow&#039;,&lt;br /&gt;
            [frame.FRAME_FACE, frame.FRAME_FACE],&lt;br /&gt;
            [width - frame.FRAME_FACE, frame.FRAME_FACE]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-shadow&#039;,&lt;br /&gt;
            [width - frame.FRAME_FACE, frame.FRAME_FACE],&lt;br /&gt;
            [width - frame.FRAME_FACE, d.ROW_HEIGHT - frame.FRAME_FACE]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-highlight&#039;,&lt;br /&gt;
            [frame.FRAME_FACE, d.ROW_HEIGHT - frame.FRAME_FACE],&lt;br /&gt;
            [frame.FRAME_FACE, frame.FRAME_FACE]&lt;br /&gt;
        ));&lt;br /&gt;
        group.appendChild(api.line(&lt;br /&gt;
            &#039;portal-category-well-highlight&#039;,&lt;br /&gt;
            [width - frame.FRAME_FACE, d.ROW_HEIGHT - frame.FRAME_FACE],&lt;br /&gt;
            [frame.FRAME_FACE, d.ROW_HEIGHT - frame.FRAME_FACE]&lt;br /&gt;
        ));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createSegment(item, index, geometry) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var frame = api.FRAME;&lt;br /&gt;
        var data = geometry.segments[index];&lt;br /&gt;
        var d = geometry.dimensions;&lt;br /&gt;
        var anchor = api.create(&#039;a&#039;, &#039;portal-cat-anchor&#039;);&lt;br /&gt;
        var outer = api.polygon(&#039;portal-cat-edge&#039;, data.points);&lt;br /&gt;
        var surfacePoints = api.segmentAt(&lt;br /&gt;
            data.leftTopX,&lt;br /&gt;
            data.rightTopX,&lt;br /&gt;
            d.BUTTON_TOP + frame.BORDER,&lt;br /&gt;
            d.BUTTON_BOTTOM - frame.BORDER,&lt;br /&gt;
            data.leftDir,&lt;br /&gt;
            data.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER,&lt;br /&gt;
            frame.BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var bevelPoints = api.segmentAt(&lt;br /&gt;
            data.leftTopX,&lt;br /&gt;
            data.rightTopX,&lt;br /&gt;
            d.BUTTON_TOP + frame.BORDER + frame.BEVEL,&lt;br /&gt;
            d.BUTTON_BOTTOM - frame.BORDER - frame.BEVEL,&lt;br /&gt;
            data.leftDir,&lt;br /&gt;
            data.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER + frame.BEVEL,&lt;br /&gt;
            frame.BORDER + frame.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
        var hit = api.polygon(&#039;portal-cat-hit&#039;, data.points);&lt;br /&gt;
        var text = api.create(&#039;text&#039;, &#039;portal-cat-label&#039;);&lt;br /&gt;
        var href = buildPath(item.title);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, item.key);&lt;br /&gt;
&lt;br /&gt;
        anchor.appendChild(outer);&lt;br /&gt;
        anchor.appendChild(api.polygon(&#039;portal-cat-surface&#039;, surfacePoints));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-top&#039;, bevelPoints[0], bevelPoints[1]));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-right&#039;, bevelPoints[1], bevelPoints[2]));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-bottom&#039;, bevelPoints[2], bevelPoints[3]));&lt;br /&gt;
        anchor.appendChild(api.line(&#039;portal-cat-inset portal-cat-inset-left&#039;, bevelPoints[3], bevelPoints[0]));&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
        anchor.appendChild(hit);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, api.centerX(surfacePoints));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, Math.round((d.BUTTON_TOP + d.BUTTON_BOTTOM) / 2) + 1);&lt;br /&gt;
        text.setAttribute(&#039;font-size&#039;, &#039;12&#039;);&lt;br /&gt;
        text.setAttribute(&#039;font-weight&#039;, &#039;700&#039;);&lt;br /&gt;
        text.setAttribute(&#039;text-anchor&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;dominant-baseline&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        text.textContent = item.label;&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        return anchor;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function publishSeams(geometry) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var segments = geometry.segments;&lt;br /&gt;
        var d = geometry.dimensions;&lt;br /&gt;
&lt;br /&gt;
        api.setSeams({&lt;br /&gt;
            width:geometry.width,&lt;br /&gt;
            rowHeight:d.ROW_HEIGHT,&lt;br /&gt;
            buttonTop:d.BUTTON_TOP,&lt;br /&gt;
            buttonBottom:d.BUTTON_BOTTOM,&lt;br /&gt;
            overlap:d.OVERLAP,&lt;br /&gt;
            bottomStartAbsY:d.BOTTOM_START_ABS_Y,&lt;br /&gt;
            settingsLeftTopX:segments[1].leftTopX,&lt;br /&gt;
            settingsLeftDir:segments[1].leftDir,&lt;br /&gt;
            corpRightTopX:segments[3].rightTopX,&lt;br /&gt;
            corpRightDir:segments[3].rightDir,&lt;br /&gt;
            projectLeftTopX:segments[2].leftTopX,&lt;br /&gt;
            projectLeftDir:segments[2].leftDir,&lt;br /&gt;
            projectRightTopX:segments[2].rightTopX,&lt;br /&gt;
            projectRightDir:segments[2].rightDir&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildSvg(width) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var geometry = calculateGeometry(width);&lt;br /&gt;
        var svg = api.create(&#039;svg&#039;, &#039;portal-category-svg&#039;);&lt;br /&gt;
&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + geometry.width + &#039; &#039; + geometry.dimensions.ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, geometry.width);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, geometry.dimensions.ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;xMidYMid meet&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;aria-hidden&#039;, &#039;false&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;data-frame-width&#039;, geometry.width);&lt;br /&gt;
&lt;br /&gt;
        appendFrame(svg, geometry);&lt;br /&gt;
        ITEMS.forEach(function (item, index) {&lt;br /&gt;
            svg.appendChild(createSegment(item, index, geometry));&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return { svg:svg, geometry:geometry };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderMount(mount) {&lt;br /&gt;
        var api = geometryApi();&lt;br /&gt;
        var width;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var result;&lt;br /&gt;
&lt;br /&gt;
        if (!mount || !api) return false;&lt;br /&gt;
        width = api.readWidth(mount);&lt;br /&gt;
        if (!width) {&lt;br /&gt;
            if (!mount.__categoryNavRetry) {&lt;br /&gt;
                mount.__categoryNavRetry = window.setTimeout(function () {&lt;br /&gt;
                    mount.__categoryNavRetry = 0;&lt;br /&gt;
                    renderMount(mount);&lt;br /&gt;
                }, 60);&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (mount.__categoryNavRetry) {&lt;br /&gt;
            window.clearTimeout(mount.__categoryNavRetry);&lt;br /&gt;
            mount.__categoryNavRetry = 0;&lt;br /&gt;
        }&lt;br /&gt;
        nav = mount.querySelector(&#039;.portal-category-nav&#039;);&lt;br /&gt;
        if (!nav) {&lt;br /&gt;
            clearNode(mount);&lt;br /&gt;
            nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
            nav.className = &#039;portal-category-nav&#039;;&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, &#039;Main categories&#039;);&lt;br /&gt;
            mount.appendChild(nav);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (nav.__frameWidth === width &amp;amp;&amp;amp; nav.querySelector(&#039;.portal-category-svg&#039;)) {&lt;br /&gt;
            publishSeams(calculateGeometry(width));&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        result = buildSvg(width);&lt;br /&gt;
        nav.__frameWidth = width;&lt;br /&gt;
        clearNode(nav);&lt;br /&gt;
        nav.appendChild(result.svg);&lt;br /&gt;
        mount.setAttribute(&#039;data-category-nav-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        publishSeams(result.geometry);&lt;br /&gt;
&lt;br /&gt;
        if (window.ResizeObserver &amp;amp;&amp;amp; !mount.__categoryNavObserver) {&lt;br /&gt;
            mount.__categoryNavObserver = new window.ResizeObserver(function () {&lt;br /&gt;
                schedule(mount);&lt;br /&gt;
            });&lt;br /&gt;
            mount.__categoryNavObserver.observe(mount);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var mounts = [];&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;)) mounts.push(scope);&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;), function (mount) {&lt;br /&gt;
            if (mounts.indexOf(mount) === -1) mounts.push(mount);&lt;br /&gt;
        });&lt;br /&gt;
        mounts.forEach(function (mount) {&lt;br /&gt;
            if (renderMount(mount)) rendered += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { renderAll(document); });&lt;br /&gt;
    } else {&lt;br /&gt;
        renderAll(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            schedule(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () { schedule(document); });&lt;br /&gt;
&lt;br /&gt;
    window.CategoryNav = {&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderAll:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
    window.CLBI = window.CLBI || {};&lt;br /&gt;
    window.CLBI.categoryNav = {&lt;br /&gt;
        init:renderAll,&lt;br /&gt;
        renderMount:renderMount&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4663</id>
		<title>미디어위키:BottomGuideNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:BottomGuideNav.js&amp;diff=4663"/>
		<updated>2026-07-17T02:15:16Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-frame-geometry-normalized-20260717 / js/BottomGuideNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
BottomGuideNav&lt;br /&gt;
대문 하단 「길라잡이」 SVG 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
상단 CategoryNav와 분리해 로드한다.&lt;br /&gt;
FrameGeometry.js가 공유한 설정–프로젝트–기업 및 공동체 seam을 사용하며,&lt;br /&gt;
상단 SVG나 화면 rect를 다시 읽지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var COMPONENT = &#039;category-guide-nav&#039;;&lt;br /&gt;
    var FRAME_API = window.CLBI &amp;amp;&amp;amp; window.CLBI.frame;&lt;br /&gt;
    var HEIGHT = FRAME_API.FRAME.BUTTON_FACE;&lt;br /&gt;
    var TOP_FRAME = FRAME_API.FRAME.FRAME_FACE;&lt;br /&gt;
    var BOTTOM_FRAME = FRAME_API.FRAME.FRAME_FACE;&lt;br /&gt;
    var GAP = FRAME_API.FRAME.GAP;&lt;br /&gt;
    var ROW_HEIGHT = TOP_FRAME + GAP + HEIGHT + GAP + BOTTOM_FRAME;&lt;br /&gt;
    var BUTTON_TOP = TOP_FRAME + GAP;&lt;br /&gt;
    var BUTTON_BOTTOM = BUTTON_TOP + HEIGHT;&lt;br /&gt;
    var COMPACT_HEIGHT = HEIGHT;&lt;br /&gt;
    var COMPACT_TOP_FRAME = TOP_FRAME;&lt;br /&gt;
    var COMPACT_BOTTOM_FRAME = BOTTOM_FRAME;&lt;br /&gt;
    var retryTimer = 0;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var guideLayoutAnimationFrame = 0;&lt;br /&gt;
    var observer = null;&lt;br /&gt;
    var manifestoIntroPromise = null;&lt;br /&gt;
    var MANIFESTO_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var MANIFESTO_INTRO_VERSION = &#039;20260713-divider-equal-gap-005&#039;;&lt;br /&gt;
    var GUIDE_DUMMY_ITEMS = [&lt;br /&gt;
        { key: &#039;start&#039;, label: &#039;처음 오셨다면&#039; },&lt;br /&gt;
        { key: &#039;world&#039;, label: &#039;세계관&#039; },&lt;br /&gt;
        { key: &#039;era&#039;, label: &#039;시대&#039; },&lt;br /&gt;
        { key: &#039;people&#039;, label: &#039;인물&#039; },&lt;br /&gt;
        { key: &#039;anecdote&#039;, label: &#039;에넥도트&#039; },&lt;br /&gt;
        { key: &#039;participate&#039;, label: &#039;참여 안내&#039; }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function revealManifesto(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        var portal = scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;) || document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var copy;&lt;br /&gt;
&lt;br /&gt;
        /* 이전 캐시에서 남은 전역 잠금을 즉시 해제한다. */&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-pending&#039;);&lt;br /&gt;
        document.documentElement.classList.remove(&#039;manifesto-intro-active&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-prepared&#039;);&lt;br /&gt;
        portal.classList.remove(&#039;manifesto-intro-running&#039;);&lt;br /&gt;
        title = portal.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        copy = portal.querySelector(&#039;.main-manifesto-copy&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (title) {&lt;br /&gt;
            title.style.opacity = &#039;1&#039;;&lt;br /&gt;
            title.style.borderBottom = &#039;0&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        if (copy) copy.style.visibility = &#039;visible&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line&#039;), function (line) {&lt;br /&gt;
            line.style.height = &#039;26px&#039;;&lt;br /&gt;
            line.style.maxHeight = &#039;26px&#039;;&lt;br /&gt;
            line.style.opacity = &#039;1&#039;;&lt;br /&gt;
            line.style.overflow = &#039;visible&#039;;&lt;br /&gt;
            line.style.clipPath = &#039;inset(0 0 0 0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.manifesto-line-inner&#039;), function (inner) {&lt;br /&gt;
            inner.style.opacity = &#039;1&#039;;&lt;br /&gt;
            inner.style.transform = &#039;translateY(0)&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifestoIntro(root) {&lt;br /&gt;
        if (&lt;br /&gt;
            window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
            window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
            typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(window.ManifestoIntro.init(root || document)).catch(function () {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    return false;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (error) {&lt;br /&gt;
                revealManifesto(root);&lt;br /&gt;
                return Promise.resolve(false);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifestoIntroPromise) return manifestoIntroPromise;&lt;br /&gt;
&lt;br /&gt;
        manifestoIntroPromise = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                if (!ok) {&lt;br /&gt;
                    revealManifesto(root);&lt;br /&gt;
                    manifestoIntroPromise = null;&lt;br /&gt;
                }&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            script.src = &#039;/index.php?title=&#039; + encodeURIComponent(&#039;MediaWiki:ManifestoIntro.js&#039;) +&lt;br /&gt;
                &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&amp;amp;v=&#039; + encodeURIComponent(MANIFESTO_INTRO_VERSION) +&lt;br /&gt;
                &#039;&amp;amp;_manifestoFresh=&#039; + encodeURIComponent(String(Date.now()));&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-manifesto-intro-script&#039;, &#039;1&#039;);&lt;br /&gt;
            script.onload = function () {&lt;br /&gt;
                if (&lt;br /&gt;
                    window.ManifestoIntro &amp;amp;&amp;amp;&lt;br /&gt;
                    window.ManifestoIntro.version === MANIFESTO_INTRO_VERSION &amp;amp;&amp;amp;&lt;br /&gt;
                    typeof window.ManifestoIntro.init === &#039;function&#039;&lt;br /&gt;
                ) {&lt;br /&gt;
                    Promise.resolve(window.ManifestoIntro.init(root || document))&lt;br /&gt;
                        .then(function () { settle(true); })&lt;br /&gt;
                        .catch(function () { settle(false); });&lt;br /&gt;
                } else {&lt;br /&gt;
                    settle(false);&lt;br /&gt;
                }&lt;br /&gt;
            };&lt;br /&gt;
            script.onerror = function () {&lt;br /&gt;
                settle(false);&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, MANIFESTO_LOAD_TIMEOUT_MS);&lt;br /&gt;
&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return manifestoIntroPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    revealManifesto(document);&lt;br /&gt;
&lt;br /&gt;
    function createSvgElement(name) {&lt;br /&gt;
        return FRAME_API.create(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildWikiPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) {&lt;br /&gt;
            return window.buildWikiPath(title);&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPolygon(className, points) {&lt;br /&gt;
        return FRAME_API.polygon(className, points);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createLine(className, start, end) {&lt;br /&gt;
        return FRAME_API.line(className, start, end);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createRect(className, x, y, width, height) {&lt;br /&gt;
        return FRAME_API.rect(className, x, y, width, height);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function centerOfPoints(points) {&lt;br /&gt;
        return [FRAME_API.centerX(points), Math.round((points[0][1] + points[2][1]) / 2)];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function shiftY(points, amount) {&lt;br /&gt;
        return points.map(function (point) {&lt;br /&gt;
            return [Math.round(point[0]), Math.round(point[1] + amount)];&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeGuideGeometry(height, topFrame, bottomFrame, sideFrameProgress) {&lt;br /&gt;
        height = Math.round(height);&lt;br /&gt;
        topFrame = Math.round(topFrame);&lt;br /&gt;
        bottomFrame = Math.round(bottomFrame);&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            height: height,&lt;br /&gt;
            topFrame: topFrame,&lt;br /&gt;
            bottomFrame: bottomFrame,&lt;br /&gt;
            buttonTop: topFrame + GAP,&lt;br /&gt;
            buttonBottom: topFrame + GAP + height,&lt;br /&gt;
            rowHeight: topFrame + GAP + height + GAP + bottomFrame,&lt;br /&gt;
            sideFrameProgress: Math.max(0, Math.min(1, Number(sideFrameProgress || 0)))&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getGuideGeometry(compact) {&lt;br /&gt;
        return compact&lt;br /&gt;
            ? makeGuideGeometry(COMPACT_HEIGHT, COMPACT_TOP_FRAME, COMPACT_BOTTOM_FRAME, 1)&lt;br /&gt;
            : makeGuideGeometry(HEIGHT, TOP_FRAME, BOTTOM_FRAME, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function interpolateGuideGeometry(from, to, progress) {&lt;br /&gt;
        function between(start, end) {&lt;br /&gt;
            return start + (end - start) * progress;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return makeGuideGeometry(&lt;br /&gt;
            between(from.height, to.height),&lt;br /&gt;
            between(from.topFrame, to.topFrame),&lt;br /&gt;
            between(from.bottomFrame, to.bottomFrame),&lt;br /&gt;
            between(from.sideFrameProgress, to.sideFrameProgress)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyGuideRowHeight(mount, nav, svg, geometry) {&lt;br /&gt;
        var height = geometry.rowHeight + &#039;px&#039;;&lt;br /&gt;
&lt;br /&gt;
        [mount, nav, svg].forEach(function (node) {&lt;br /&gt;
            if (!node || !node.style) return;&lt;br /&gt;
            node.style.setProperty(&#039;height&#039;, height, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;min-height&#039;, height, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;max-height&#039;, height, &#039;important&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestGuideFrame(callback) {&lt;br /&gt;
        if (typeof window.requestAnimationFrame === &#039;function&#039;) {&lt;br /&gt;
            return window.requestAnimationFrame(callback);&lt;br /&gt;
        }&lt;br /&gt;
        return window.setTimeout(function () { callback(Date.now()); }, 16);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function cancelGuideFrame(handle) {&lt;br /&gt;
        if (!handle) return;&lt;br /&gt;
        if (typeof window.cancelAnimationFrame === &#039;function&#039;) {&lt;br /&gt;
            window.cancelAnimationFrame(handle);&lt;br /&gt;
        } else {&lt;br /&gt;
            window.clearTimeout(handle);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function animateGuideLayout(portal, compact) {&lt;br /&gt;
        var from = portal &amp;amp;&amp;amp; portal.GuideNavGeometry&lt;br /&gt;
            ? portal.GuideNavGeometry&lt;br /&gt;
            : getGuideGeometry(!compact);&lt;br /&gt;
        var to = getGuideGeometry(compact);&lt;br /&gt;
        var startedAt = null;&lt;br /&gt;
        var duration = 340;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return;&lt;br /&gt;
        if (guideLayoutAnimationFrame) {&lt;br /&gt;
            cancelGuideFrame(guideLayoutAnimationFrame);&lt;br /&gt;
            guideLayoutAnimationFrame = 0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (window.matchMedia &amp;amp;&amp;amp; window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            portal.GuideNavGeometry = to;&lt;br /&gt;
            render(portal, to);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function step(now) {&lt;br /&gt;
            var elapsed;&lt;br /&gt;
            var progress;&lt;br /&gt;
            var eased;&lt;br /&gt;
&lt;br /&gt;
            if (startedAt === null) startedAt = now;&lt;br /&gt;
            elapsed = Math.min(1, (now - startedAt) / duration);&lt;br /&gt;
            eased = 1 - Math.pow(1 - elapsed, 3);&lt;br /&gt;
            progress = interpolateGuideGeometry(from, to, eased);&lt;br /&gt;
            portal.GuideNavGeometry = progress;&lt;br /&gt;
            render(portal, progress);&lt;br /&gt;
&lt;br /&gt;
            if (elapsed &amp;lt; 1) {&lt;br /&gt;
                guideLayoutAnimationFrame = requestGuideFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.GuideNavGeometry = to;&lt;br /&gt;
                guideLayoutAnimationFrame = 0;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        guideLayoutAnimationFrame = requestGuideFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildGuideButton(geometry) {&lt;br /&gt;
        var seams = FRAME_API.seams;&lt;br /&gt;
        var leftTop;&lt;br /&gt;
        var rightTop;&lt;br /&gt;
        var leftDir;&lt;br /&gt;
        var rightDir;&lt;br /&gt;
&lt;br /&gt;
        if (!seams || !seams.width) return null;&lt;br /&gt;
        leftTop = FRAME_API.slantX(&lt;br /&gt;
            seams.settingsLeftTopX,&lt;br /&gt;
            seams.buttonBottom,&lt;br /&gt;
            seams.settingsLeftDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        rightTop = FRAME_API.slantX(&lt;br /&gt;
            seams.corpRightTopX,&lt;br /&gt;
            seams.buttonBottom,&lt;br /&gt;
            seams.corpRightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        leftDir = -seams.settingsLeftDir;&lt;br /&gt;
        rightDir = -seams.corpRightDir;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            leftTopX:leftTop,&lt;br /&gt;
            rightTopX:rightTop,&lt;br /&gt;
            leftDir:leftDir,&lt;br /&gt;
            rightDir:rightDir,&lt;br /&gt;
            points:shiftY(&lt;br /&gt;
                FRAME_API.segment(leftTop, rightTop, geometry.height, leftDir, rightDir, 0),&lt;br /&gt;
                geometry.buttonTop&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function guideLayer(button, geometry, amount) {&lt;br /&gt;
        return shiftY(&lt;br /&gt;
            FRAME_API.segment(&lt;br /&gt;
                button.leftTopX + amount,&lt;br /&gt;
                button.rightTopX - amount,&lt;br /&gt;
                geometry.height - amount * 2,&lt;br /&gt;
                button.leftDir,&lt;br /&gt;
                button.rightDir,&lt;br /&gt;
                amount % FRAME_API.FRAME.SLANT_RISE&lt;br /&gt;
            ),&lt;br /&gt;
            geometry.buttonTop + amount&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendContinuousBottomFrame(svg, button, width, geometry) {&lt;br /&gt;
        var frame = FRAME_API.FRAME;&lt;br /&gt;
        var group = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var leftTarget = button.points[0][0] - frame.FRAME_FACE - frame.GAP;&lt;br /&gt;
        var rightTarget = button.points[1][0] + frame.FRAME_FACE + frame.GAP;&lt;br /&gt;
        var leftBase = leftTarget - FRAME_API.slantX(0, geometry.buttonTop, button.leftDir, 0);&lt;br /&gt;
        var rightBase = rightTarget - FRAME_API.slantX(0, geometry.buttonTop, button.rightDir, 0);&lt;br /&gt;
        var outer = FRAME_API.segment(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.rowHeight,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0&lt;br /&gt;
        );&lt;br /&gt;
        var well = FRAME_API.segmentAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            geometry.topFrame,&lt;br /&gt;
            geometry.rowHeight - geometry.bottomFrame,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.FRAME_FACE,&lt;br /&gt;
            frame.FRAME_FACE&lt;br /&gt;
        );&lt;br /&gt;
        var bevel = FRAME_API.segmentAt(&lt;br /&gt;
            leftBase,&lt;br /&gt;
            rightBase,&lt;br /&gt;
            frame.BORDER + frame.BEVEL,&lt;br /&gt;
            geometry.rowHeight - frame.BORDER - frame.BEVEL,&lt;br /&gt;
            button.leftDir,&lt;br /&gt;
            button.rightDir,&lt;br /&gt;
            0,&lt;br /&gt;
            frame.BORDER + frame.BEVEL,&lt;br /&gt;
            frame.BORDER + frame.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        group.setAttribute(&#039;class&#039;, &#039;portal-guide-arch-frame&#039;);&lt;br /&gt;
        group.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-guide-arch-outer&#039;, outer));&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-guide-arch-well&#039;, well));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[3], outer[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[0], outer[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-edge&#039;, outer[1], outer[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-highlight&#039;, bevel[0], bevel[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-highlight&#039;, bevel[1], bevel[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-shadow&#039;, bevel[3], bevel[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-highlight&#039;, well[0], well[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-highlight&#039;, well[1], well[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-guide-arch-inner-shadow&#039;, well[3], well[0]));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyPanel(portal) {&lt;br /&gt;
        var panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        var manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) {&lt;br /&gt;
                    topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                } else {&lt;br /&gt;
                    topMount.parentNode.appendChild(panel);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.insertBefore(well, panel.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) {&lt;br /&gt;
            well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            panel: panel,&lt;br /&gt;
            well: well&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBodyScreenHidden(screen, hidden) {&lt;br /&gt;
        if (!screen) return;&lt;br /&gt;
&lt;br /&gt;
        screen.classList.toggle(&#039;is-active&#039;, !hidden);&lt;br /&gt;
        screen.hidden = hidden;&lt;br /&gt;
        screen.setAttribute(&#039;aria-hidden&#039;, hidden ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateGuideButtonState(portal, active) {&lt;br /&gt;
        var guideActive = active === &#039;guide&#039;;&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var target = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-anchor&#039;), function (anchor) {&lt;br /&gt;
            anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
            anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            anchor.setAttribute(&#039;data-main-body-target&#039;, target);&lt;br /&gt;
            anchor.removeAttribute(&#039;aria-current&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-label&#039;), function (text) {&lt;br /&gt;
            text.textContent = label;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(portal.querySelectorAll(&#039;.portal-guide-nav&#039;), function (nav) {&lt;br /&gt;
            nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureGuideDummyMenu(guide) {&lt;br /&gt;
        var menu;&lt;br /&gt;
&lt;br /&gt;
        if (!guide) return null;&lt;br /&gt;
&lt;br /&gt;
        menu = guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
        if (menu) return menu;&lt;br /&gt;
&lt;br /&gt;
        menu = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        menu.className = &#039;main-guide-menu&#039;;&lt;br /&gt;
        menu.setAttribute(&#039;data-guide-dummy-menu&#039;, &#039;1&#039;);&lt;br /&gt;
        menu.setAttribute(&#039;aria-label&#039;, &#039;길라잡이 항목 예시&#039;);&lt;br /&gt;
&lt;br /&gt;
        GUIDE_DUMMY_ITEMS.forEach(function (item, index) {&lt;br /&gt;
            var wrapper = document.createElement(&#039;div&#039;);&lt;br /&gt;
            var button = document.createElement(&#039;button&#039;);&lt;br /&gt;
            var number = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            var arrow = document.createElement(&#039;span&#039;);&lt;br /&gt;
&lt;br /&gt;
            wrapper.className = &#039;main-guide-menu-item&#039;;&lt;br /&gt;
            wrapper.style.setProperty(&#039;--guide-menu-order&#039;, String(index));&lt;br /&gt;
&lt;br /&gt;
            button.type = &#039;button&#039;;&lt;br /&gt;
            button.className = &#039;main-guide-menu-button&#039;;&lt;br /&gt;
            button.setAttribute(&#039;data-guide-dummy-button&#039;, item.key);&lt;br /&gt;
            button.setAttribute(&#039;aria-label&#039;, item.label + &#039; (더미 버튼)&#039;);&lt;br /&gt;
&lt;br /&gt;
            number.className = &#039;main-guide-menu-number&#039;;&lt;br /&gt;
            number.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            number.textContent = String(index + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
            label.className = &#039;main-guide-menu-label&#039;;&lt;br /&gt;
            label.textContent = item.label;&lt;br /&gt;
&lt;br /&gt;
            arrow.className = &#039;main-guide-menu-arrow&#039;;&lt;br /&gt;
            arrow.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            arrow.textContent = &#039;›&#039;;&lt;br /&gt;
&lt;br /&gt;
            button.appendChild(number);&lt;br /&gt;
            button.appendChild(label);&lt;br /&gt;
            button.appendChild(arrow);&lt;br /&gt;
            button.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
            });&lt;br /&gt;
            wrapper.appendChild(button);&lt;br /&gt;
            menu.appendChild(wrapper);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        guide.insertBefore(menu, guide.firstChild);&lt;br /&gt;
        return menu;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function restartGuideMenuAnimation(guide) {&lt;br /&gt;
        if (!guide || !guide.querySelector(&#039;[data-guide-dummy-menu=&amp;quot;1&amp;quot;]&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        /* 같은 화면에 재진입해도 첫 프레임부터 순차 진입을 다시 시작한다. */&lt;br /&gt;
        void guide.offsetWidth;&lt;br /&gt;
        guide.classList.add(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBodyScreens(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var manifesto = layout.well.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
        var guide = layout.well.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;);&lt;br /&gt;
        var panels;&lt;br /&gt;
        var active = layout.well.getAttribute(&#039;data-active-main-body-screen&#039;) ||&lt;br /&gt;
            portal.getAttribute(&#039;data-main-body-screen&#039;) ||&lt;br /&gt;
            &#039;manifesto&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (manifesto) {&lt;br /&gt;
            manifesto.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            manifesto.setAttribute(&#039;data-main-body-screen&#039;, &#039;manifesto&#039;);&lt;br /&gt;
            if (!manifesto.id) manifesto.id = &#039;main-manifesto-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!guide) {&lt;br /&gt;
            guide = document.createElement(&#039;section&#039;);&lt;br /&gt;
            guide.className = &#039;main-body-screen main-guide-screen&#039;;&lt;br /&gt;
            guide.setAttribute(&#039;data-main-body-screen&#039;, &#039;guide&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;data-main-guide-screen-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;aria-label&#039;, &#039;길라잡이&#039;);&lt;br /&gt;
            guide.setAttribute(&#039;role&#039;, &#039;region&#039;);&lt;br /&gt;
            guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
            layout.well.appendChild(guide);&lt;br /&gt;
        } else {&lt;br /&gt;
            guide.classList.add(&#039;main-body-screen&#039;);&lt;br /&gt;
            guide.classList.add(&#039;main-guide-screen&#039;);&lt;br /&gt;
            if (!guide.id) guide.id = &#039;main-guide-screen&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        ensureGuideDummyMenu(guide);&lt;br /&gt;
&lt;br /&gt;
        panels = Array.prototype.slice.call(&lt;br /&gt;
            layout.well.querySelectorAll(&#039;.main-body-screen[data-main-body-screen]&#039;)&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        if (active !== &#039;guide&#039;) active = &#039;manifesto&#039;;&lt;br /&gt;
        if (!panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === active;&lt;br /&gt;
        })) {&lt;br /&gt;
            active = &#039;manifesto&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(&lt;br /&gt;
                panel,&lt;br /&gt;
                panel.getAttribute(&#039;data-main-body-screen&#039;) !== active&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        layout.well.setAttribute(&#039;data-main-body-screen-controller-ready&#039;, &#039;1&#039;);&lt;br /&gt;
        layout.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; !guide.classList.contains(&#039;is-guide-menu-entering&#039;)) {&lt;br /&gt;
            restartGuideMenuAnimation(guide);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            well: layout.well,&lt;br /&gt;
            panels: panels,&lt;br /&gt;
            manifesto: manifesto,&lt;br /&gt;
            guide: guide,&lt;br /&gt;
            active: active&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBodyScreen(portal, screenName) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var previous;&lt;br /&gt;
        var active;&lt;br /&gt;
        var event;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        screenName = screenName === &#039;guide&#039; ? &#039;guide&#039; : &#039;manifesto&#039;;&lt;br /&gt;
        if (!screens.panels.some(function (panel) {&lt;br /&gt;
            return panel.getAttribute(&#039;data-main-body-screen&#039;) === screenName;&lt;br /&gt;
        })) return false;&lt;br /&gt;
&lt;br /&gt;
        previous = screens.active;&lt;br /&gt;
        active = screenName;&lt;br /&gt;
&lt;br /&gt;
        screens.panels.forEach(function (panel) {&lt;br /&gt;
            setBodyScreenHidden(&lt;br /&gt;
                panel,&lt;br /&gt;
                panel.getAttribute(&#039;data-main-body-screen&#039;) !== active&lt;br /&gt;
            );&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        screens.well.setAttribute(&#039;data-active-main-body-screen&#039;, active);&lt;br /&gt;
        portal.setAttribute(&#039;data-main-body-screen&#039;, active);&lt;br /&gt;
        portal.classList.toggle(&#039;is-guide-screen-active&#039;, active === &#039;guide&#039;);&lt;br /&gt;
        updateGuideButtonState(portal, active);&lt;br /&gt;
&lt;br /&gt;
        if (active === &#039;guide&#039; &amp;amp;&amp;amp; previous !== active) {&lt;br /&gt;
            restartGuideMenuAnimation(screens.guide);&lt;br /&gt;
        } else if (active !== &#039;guide&#039; &amp;amp;&amp;amp; screens.guide) {&lt;br /&gt;
            screens.guide.classList.remove(&#039;is-guide-menu-entering&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            animateGuideLayout(portal, active === &#039;guide&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (previous !== active) {&lt;br /&gt;
            try {&lt;br /&gt;
                event = new CustomEvent(&#039;mainbodyscreenchange&#039;, {&lt;br /&gt;
                    bubbles: true,&lt;br /&gt;
                    detail: { screen: active, previous: previous }&lt;br /&gt;
                });&lt;br /&gt;
                portal.dispatchEvent(event);&lt;br /&gt;
            } catch (error) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function toggleBodyScreen(portal) {&lt;br /&gt;
        var screens;&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!portal) return false;&lt;br /&gt;
        screens = ensureBodyScreens(portal);&lt;br /&gt;
        target = screens.active === &#039;guide&#039; ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        return activateBodyScreen(portal, target);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleGuideScreenCapture(event) {&lt;br /&gt;
        var target;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var portal;&lt;br /&gt;
&lt;br /&gt;
        if (!event || event.__mainGuideScreenHandled) return;&lt;br /&gt;
        if (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        target = event.target;&lt;br /&gt;
        anchor = target &amp;amp;&amp;amp; target.closest&lt;br /&gt;
            ? target.closest(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)&lt;br /&gt;
            : null;&lt;br /&gt;
        if (!anchor) return;&lt;br /&gt;
&lt;br /&gt;
        portal = anchor.closest ? anchor.closest(&#039;.main-portal&#039;) : null;&lt;br /&gt;
        if (!portal || !toggleBodyScreen(portal)) return;&lt;br /&gt;
&lt;br /&gt;
        event.__mainGuideScreenHandled = true;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) {&lt;br /&gt;
            event.stopImmediatePropagation();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    이전 실행본의 ResizeObserver가 오래된 문서 링크를 다시 그려도&lt;br /&gt;
    전역 SPA 링크 처리보다 먼저 화면 전환으로 고정한다.&lt;br /&gt;
    */&lt;br /&gt;
    if (window.BottomGuideNavScreenCaptureHandler) {&lt;br /&gt;
        document.removeEventListener(&#039;click&#039;, window.BottomGuideNavScreenCaptureHandler, true);&lt;br /&gt;
    }&lt;br /&gt;
    window.BottomGuideNavScreenCaptureHandler = handleGuideScreenCapture;&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleGuideScreenCapture, true);&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal) {&lt;br /&gt;
        var layout = ensureBodyPanel(portal);&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        ensureBodyScreens(portal);&lt;br /&gt;
&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
            mount.setAttribute(&#039;data-bottom-guide-generated&#039;, &#039;1&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        길라잡이는 우물 안의 독립 버튼이 아니라&lt;br /&gt;
        main-body-panel 하단 프레임에 융합된 돌출부다.&lt;br /&gt;
        */&lt;br /&gt;
        if (mount.parentNode !== layout.panel) {&lt;br /&gt;
            layout.panel.appendChild(mount);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(portal, suppliedGeometry) {&lt;br /&gt;
        var mount = ensureMount(portal);&lt;br /&gt;
        var width = FRAME_API.readWidth(mount);&lt;br /&gt;
        var guideActive = portal.getAttribute(&#039;data-main-body-screen&#039;) === &#039;guide&#039;;&lt;br /&gt;
        var geometry = suppliedGeometry || portal.GuideNavGeometry || getGuideGeometry(guideActive);&lt;br /&gt;
&lt;br /&gt;
        if (!width) return false;&lt;br /&gt;
&lt;br /&gt;
        portal.GuideNavGeometry = geometry;&lt;br /&gt;
&lt;br /&gt;
        var button = buildGuideButton(geometry);&lt;br /&gt;
        var points;&lt;br /&gt;
        var surfacePoints;&lt;br /&gt;
        var insetPoints;&lt;br /&gt;
        var center;&lt;br /&gt;
&lt;br /&gt;
        if (!button) return false;&lt;br /&gt;
        points = button.points;&lt;br /&gt;
        surfacePoints = guideLayer(button, geometry, FRAME_API.FRAME.BORDER);&lt;br /&gt;
        insetPoints = guideLayer(&lt;br /&gt;
            button,&lt;br /&gt;
            geometry,&lt;br /&gt;
            FRAME_API.FRAME.BORDER + FRAME_API.FRAME.BEVEL&lt;br /&gt;
        );&lt;br /&gt;
        center = centerOfPoints(surfacePoints);&lt;br /&gt;
        var nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        var svg = createSvgElement(&#039;svg&#039;);&lt;br /&gt;
        var anchor = createSvgElement(&#039;a&#039;);&lt;br /&gt;
        var buttonGeometry = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var label = guideActive ? &#039;돌아가기&#039; : &#039;길라잡이&#039;;&lt;br /&gt;
        var targetScreen = guideActive ? &#039;manifesto&#039; : &#039;guide&#039;;&lt;br /&gt;
        var href = guideActive ? &#039;#main-manifesto-screen&#039; : &#039;#main-guide-screen&#039;;&lt;br /&gt;
        var text = createSvgElement(&#039;text&#039;);&lt;br /&gt;
        var outer = createPolygon(&#039;portal-cat-edge&#039;, points);&lt;br /&gt;
        var surface = createPolygon(&#039;portal-cat-surface&#039;, surfacePoints);&lt;br /&gt;
        var hit = createPolygon(&#039;portal-cat-hit&#039;, points);&lt;br /&gt;
&lt;br /&gt;
        nav.className = &#039;portal-category-nav portal-guide-nav is-continuous-weld&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
&lt;br /&gt;
        svg.setAttribute(&#039;class&#039;, &#039;portal-category-svg portal-guide-svg&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + width + &#039; &#039; + geometry.rowHeight);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, width);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, geometry.rowHeight);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;xMidYMid meet&#039;);&lt;br /&gt;
&lt;br /&gt;
        appendContinuousBottomFrame(svg, button, width, geometry);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;class&#039;, &#039;portal-cat-anchor portal-guide-anchor&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, label);&lt;br /&gt;
        anchor.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-pressed&#039;, guideActive ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, &#039;guide&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-main-body-target&#039;, targetScreen);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-controls&#039;, &#039;main-manifesto-screen main-guide-screen&#039;);&lt;br /&gt;
        buttonGeometry.setAttribute(&#039;class&#039;, &#039;portal-guide-button-geometry&#039;);&lt;br /&gt;
&lt;br /&gt;
        anchor.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            if (&lt;br /&gt;
                event &amp;amp;&amp;amp;&lt;br /&gt;
                (event.button &amp;gt; 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)&lt;br /&gt;
            ) {&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (toggleBodyScreen(portal)) {&lt;br /&gt;
                if (event) event.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        outer.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        surface.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;class&#039;, &#039;portal-cat-label portal-guide-label&#039;);&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, Math.round(center[0]));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, Math.round((geometry.buttonTop + geometry.buttonBottom) / 2) + 1);&lt;br /&gt;
        text.setAttribute(&#039;font-size&#039;, &#039;12&#039;);&lt;br /&gt;
        text.setAttribute(&#039;font-weight&#039;, &#039;700&#039;);&lt;br /&gt;
        text.setAttribute(&#039;text-anchor&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;dominant-baseline&#039;, &#039;middle&#039;);&lt;br /&gt;
        text.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        text.textContent = label;&lt;br /&gt;
&lt;br /&gt;
        buttonGeometry.appendChild(outer);&lt;br /&gt;
        buttonGeometry.appendChild(surface);&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-top&#039;,&lt;br /&gt;
            insetPoints[0],&lt;br /&gt;
            insetPoints[1]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-right&#039;,&lt;br /&gt;
            insetPoints[1],&lt;br /&gt;
            insetPoints[2]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-bottom&#039;,&lt;br /&gt;
            insetPoints[2],&lt;br /&gt;
            insetPoints[3]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(createLine(&lt;br /&gt;
            &#039;portal-cat-inset portal-cat-inset-left&#039;,&lt;br /&gt;
            insetPoints[3],&lt;br /&gt;
            insetPoints[0]&lt;br /&gt;
        ));&lt;br /&gt;
        buttonGeometry.appendChild(hit);&lt;br /&gt;
        anchor.appendChild(buttonGeometry);&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        svg.appendChild(anchor);&lt;br /&gt;
        nav.appendChild(svg);&lt;br /&gt;
&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        applyGuideRowHeight(mount, nav, svg, geometry);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-bottom-guide-width&#039;, String(width));&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findPortal(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)&lt;br /&gt;
            ? scope&lt;br /&gt;
            : scope.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(root, attempt) {&lt;br /&gt;
        window.clearTimeout(retryTimer);&lt;br /&gt;
&lt;br /&gt;
        retryTimer = window.setTimeout(function () {&lt;br /&gt;
            var portal = findPortal(root);&lt;br /&gt;
&lt;br /&gt;
            if (!portal) return;&lt;br /&gt;
&lt;br /&gt;
            if (!render(portal) &amp;amp;&amp;amp; attempt &amp;lt; 20) {&lt;br /&gt;
                schedule(root, attempt + 1);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (observer) observer.disconnect();&lt;br /&gt;
&lt;br /&gt;
            if (window.ResizeObserver) {&lt;br /&gt;
                observer = new ResizeObserver(function () {&lt;br /&gt;
                    window.clearTimeout(resizeTimer);&lt;br /&gt;
                    resizeTimer = window.setTimeout(function () {&lt;br /&gt;
                        render(portal);&lt;br /&gt;
                    }, 60);&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                observer.observe(portal);&lt;br /&gt;
                var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
                var guideMount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
                if (topMount) observer.observe(topMount);&lt;br /&gt;
                if (guideMount) observer.observe(guideMount);&lt;br /&gt;
            }&lt;br /&gt;
        }, attempt ? 60 : 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot(root) {&lt;br /&gt;
        var scope = root || document;&lt;br /&gt;
        schedule(scope, 0);&lt;br /&gt;
        loadManifestoIntro(scope);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {&lt;br /&gt;
            boot(document);&lt;br /&gt;
        }, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* 일반 링크 진입과 새로고침의 최종 로드 시점도 명시적으로 보장한다. */&lt;br /&gt;
    window.addEventListener(&#039;load&#039;, function () {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    }, { once: true });&lt;br /&gt;
&lt;br /&gt;
    /* bfcache 복원과 브라우저 뒤로/앞으로 이동에서도 새 DOM만 초기화한다. */&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, function () {&lt;br /&gt;
        boot(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            boot($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BottomGuideNav = {&lt;br /&gt;
        version: &#039;20260717-shared-frame-reverse-slant-001&#039;,&lt;br /&gt;
        render: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? render(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        activate: function (screenName) {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? activateBodyScreen(portal, screenName) : false;&lt;br /&gt;
        },&lt;br /&gt;
        toggle: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            return portal ? toggleBodyScreen(portal) : false;&lt;br /&gt;
        },&lt;br /&gt;
        status: function () {&lt;br /&gt;
            var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
            var mount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
            return {&lt;br /&gt;
                loaded: true,&lt;br /&gt;
                portal: !!portal,&lt;br /&gt;
                mount: !!mount,&lt;br /&gt;
                rendered: !!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-svg&#039;)),&lt;br /&gt;
                generated: !!(mount &amp;amp;&amp;amp; mount.getAttribute(&#039;data-bottom-guide-generated&#039;)),&lt;br /&gt;
                panel: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;)),&lt;br /&gt;
                well: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-well&#039;)),&lt;br /&gt;
                insidePanel: !!(mount &amp;amp;&amp;amp; mount.parentNode &amp;amp;&amp;amp; mount.parentNode.classList.contains(&#039;main-body-panel&#039;)),&lt;br /&gt;
                welded: !!(mount &amp;amp;&amp;amp; mount.querySelector(&#039;.portal-guide-nav.is-continuous-weld&#039;)),&lt;br /&gt;
                activeScreen: portal ? portal.getAttribute(&#039;data-main-body-screen&#039;) || &#039;manifesto&#039; : &#039;&#039;,&lt;br /&gt;
                guideScreen: !!(portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-main-body-screen=&amp;quot;guide&amp;quot;]&#039;)),&lt;br /&gt;
                width: mount ? mount.getAttribute(&#039;data-bottom-guide-width&#039;) || &#039;&#039; : &#039;&#039;&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4662</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4662"/>
		<updated>2026-07-17T02:15:16Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-frame-geometry-normalized-20260717 / css/MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:36px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
설정 왼쪽부터 기업 및 공동체 오른쪽까지의 실제 SVG 경계를 읽어&lt;br /&gt;
중앙 프레임의 최외곽 폭으로 사용한다. 좌우 투명 영역은 새 프레임으로&lt;br /&gt;
채우지 않는다. 형상은 위가 넓고 아래가 좁아지도록 뒤집고, 기존 상단&lt;br /&gt;
프레임 하단 9px 안으로 겹쳐 하나로 접합한다.&lt;br /&gt;
좌우 경사는 기존 설정·기업 버튼 사선의 화면상 기울기를 그대로 연장한다.&lt;br /&gt;
외곽·프레임 면·우물·버튼의 x 좌표도 같은 직선식으로 투영해 전 층의&lt;br /&gt;
사선이 서로 평행하고 끊김 없이 이어지게 한다.&lt;br /&gt;
두 프레임이 실제로 포개지는 상단 8px에서는 #050505를 #1d1d1d 면으로&lt;br /&gt;
덮는다. 좌우 방향선은 외곽선에서 재계산하지 않고 기존 설정 버튼의 좌측&lt;br /&gt;
인셋선과 기업 버튼의 우측 인셋선을 직접 읽어 같은 직선으로 연장한다.&lt;br /&gt;
&lt;br /&gt;
초기 길라잡이 규격:&lt;br /&gt;
- 버튼 외곽 22px&lt;br /&gt;
- 프레임 실색 #1d1d1d 7px&lt;br /&gt;
- 우물 간격 2px&lt;br /&gt;
- 전체 부착 행 43px&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 43px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:43px !important;&lt;br /&gt;
    min-height:43px !important;&lt;br /&gt;
    max-height:43px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:43px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-outline {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:10%;&lt;br /&gt;
    right:auto;&lt;br /&gt;
    width:80%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-svg {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 42px 본체·광원 + 상하 1px #050505 외곽선으로 44px이다.&lt;br /&gt;
   상하 2px #080808 간격, 상단 9px·하단 8px 프레임을 포함한 전체 행은 65px이다.&lt;br /&gt;
   상단 9px 중 외곽선·광원선을 제외한 #1d1d1d 실색 영역은 7px이다. */&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단 총 36px:&lt;br /&gt;
4px 프레임 + 2px #080808 + 24px 버튼 + 2px #080808 + 4px 프레임&lt;br /&gt;
&lt;br /&gt;
하단 총 65px:&lt;br /&gt;
중앙에서 위로 솟은 패널 하단 프레임 + 2px #080808&lt;br /&gt;
+ 44px 길라잡이 외곽 + 2px #080808 + 공유 하단 프레임&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:24px !important;&lt;br /&gt;
    min-height:24px !important;&lt;br /&gt;
    max-height:24px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:22px !important;&lt;br /&gt;
    min-height:22px !important;&lt;br /&gt;
    max-height:22px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
사선 프레임 단일 정수 좌표계 / 20260717&lt;br /&gt;
FrameGeometry.js의 7 / 2 / 20 / 2 / 7 단면을 그대로 표시한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 38px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav,&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-highlight,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-highlight,&lt;br /&gt;
.main-portal .portal-category-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame-shadow,&lt;br /&gt;
.main-portal .portal-category-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:focus .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 38px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-frame,&lt;br /&gt;
.main-portal .portal-section-frame,&lt;br /&gt;
.main-portal .portal-category-pillar,&lt;br /&gt;
.main-portal .portal-section-button-geometry,&lt;br /&gt;
.main-portal .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-hit {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 길라잡이도 같은 7 / 2 / 20 / 2 / 7 단면과 반대 사선을 사용한다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:38px !important;&lt;br /&gt;
    min-height:38px !important;&lt;br /&gt;
    max-height:38px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    stroke:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* main-body-panel의 1px 좌우 외곽선을 보정해 상단 seam 좌표폭과 일치시킨다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    left:-1px !important;&lt;br /&gt;
    right:-1px !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:PortalSectionNav.js&amp;diff=4661</id>
		<title>미디어위키:PortalSectionNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:PortalSectionNav.js&amp;diff=4661"/>
		<updated>2026-07-17T00:52:10Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-bottom-visible-gap-2px-20260717 / PortalSectionNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
PortalSectionNav&lt;br /&gt;
대문 상단 카테고리 하단 부착형 섹션 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var COMPONENT = &#039;portal-section-nav&#039;;&lt;br /&gt;
    var VERSION = &#039;20260717-portal-section-bottom-gap-two-045&#039;;&lt;br /&gt;
    var BUTTON_HEIGHT = 22;&lt;br /&gt;
    var TOP_FRAME = 8;&lt;br /&gt;
    var BOTTOM_FRAME = 8;&lt;br /&gt;
    var GAP = 2;&lt;br /&gt;
    var BOTTOM_GAP = 3;&lt;br /&gt;
    var ROW_HEIGHT = TOP_FRAME + GAP + BUTTON_HEIGHT + BOTTOM_GAP + BOTTOM_FRAME;&lt;br /&gt;
    var BUTTON_TOP = TOP_FRAME + GAP;&lt;br /&gt;
    var BUTTON_BOTTOM = BUTTON_TOP + BUTTON_HEIGHT;&lt;br /&gt;
    var OUTER_BORDER = 1;&lt;br /&gt;
    var INSET_OFFSET = 1.5;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
    var observer = null;&lt;br /&gt;
    var ITEMS = [&lt;br /&gt;
        { key:&#039;black-ice&#039;, label:&#039;블랙 아이스&#039;, title:&#039;대문&#039; },&lt;br /&gt;
        {&lt;br /&gt;
            key:&#039;anecdote&#039;,&lt;br /&gt;
            label:&#039;에넥도트&#039;,&lt;br /&gt;
            href:&#039;/index.php/특수:모든문서?namespace=3000&#039;&lt;br /&gt;
        }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function createSvgElement(name) {&lt;br /&gt;
        return document.createElementNS(SVG_NS, name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildWikiPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) {&lt;br /&gt;
            return window.buildWikiPath(title);&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function roundCoordinate(value) {&lt;br /&gt;
        return Math.round(value * 10000) / 10000;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function pointsToString(points) {&lt;br /&gt;
        return points.map(function (point) {&lt;br /&gt;
            return roundCoordinate(point[0]) + &#039;,&#039; + roundCoordinate(point[1]);&lt;br /&gt;
        }).join(&#039; &#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function parsePoints(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).trim().split(/\s+/).map(function (pair) {&lt;br /&gt;
            var parts = pair.split(&#039;,&#039;);&lt;br /&gt;
            return [Number(parts[0]), Number(parts[1])];&lt;br /&gt;
        }).filter(function (point) {&lt;br /&gt;
            return Number.isFinite(point[0]) &amp;amp;&amp;amp; Number.isFinite(point[1]);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getCentroid(points) {&lt;br /&gt;
        var x = 0;&lt;br /&gt;
        var y = 0;&lt;br /&gt;
&lt;br /&gt;
        points.forEach(function (point) {&lt;br /&gt;
            x += point[0];&lt;br /&gt;
            y += point[1];&lt;br /&gt;
        });&lt;br /&gt;
        return [x / points.length, y / points.length];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function lineXAtY(pointA, pointB, y) {&lt;br /&gt;
        var dy = pointB[1] - pointA[1];&lt;br /&gt;
        if (Math.abs(dy) &amp;lt; 0.000001) return pointA[0];&lt;br /&gt;
        return pointA[0] + (y - pointA[1]) * (pointB[0] - pointA[0]) / dy;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function projectSidesToY(points, top, bottom, horizontalInset) {&lt;br /&gt;
        var inset = Number(horizontalInset) || 0;&lt;br /&gt;
        return [&lt;br /&gt;
            [lineXAtY(points[0], points[3], top) + inset, top],&lt;br /&gt;
            [lineXAtY(points[1], points[2], top) - inset, top],&lt;br /&gt;
            [lineXAtY(points[1], points[2], bottom) - inset, bottom],&lt;br /&gt;
            [lineXAtY(points[0], points[3], bottom) + inset, bottom]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPolygon(className, points) {&lt;br /&gt;
        var polygon = createSvgElement(&#039;polygon&#039;);&lt;br /&gt;
        polygon.setAttribute(&#039;class&#039;, className);&lt;br /&gt;
        polygon.setAttribute(&#039;points&#039;, pointsToString(points));&lt;br /&gt;
        polygon.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        return polygon;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createLine(className, start, end) {&lt;br /&gt;
        var line = createSvgElement(&#039;line&#039;);&lt;br /&gt;
        line.setAttribute(&#039;class&#039;, className);&lt;br /&gt;
        line.setAttribute(&#039;x1&#039;, roundCoordinate(start[0]));&lt;br /&gt;
        line.setAttribute(&#039;y1&#039;, roundCoordinate(start[1]));&lt;br /&gt;
        line.setAttribute(&#039;x2&#039;, roundCoordinate(end[0]));&lt;br /&gt;
        line.setAttribute(&#039;y2&#039;, roundCoordinate(end[1]));&lt;br /&gt;
        line.setAttribute(&#039;vector-effect&#039;, &#039;non-scaling-stroke&#039;);&lt;br /&gt;
        line.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        return line;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getViewBox(svg) {&lt;br /&gt;
        var base = svg &amp;amp;&amp;amp; svg.viewBox &amp;amp;&amp;amp; svg.viewBox.baseVal;&lt;br /&gt;
        var values;&lt;br /&gt;
&lt;br /&gt;
        if (base &amp;amp;&amp;amp; base.width &amp;gt; 0 &amp;amp;&amp;amp; base.height &amp;gt; 0) {&lt;br /&gt;
            return { x:base.x, y:base.y, width:base.width, height:base.height };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        values = String(svg.getAttribute(&#039;viewBox&#039;) || &#039;&#039;).trim().split(/[\s,]+/).map(Number);&lt;br /&gt;
        if (values.length === 4 &amp;amp;&amp;amp; values[2] &amp;gt; 0 &amp;amp;&amp;amp; values[3] &amp;gt; 0) {&lt;br /&gt;
            return { x:values[0], y:values[1], width:values[2], height:values[3] };&lt;br /&gt;
        }&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal, topMount) {&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (mount.parentNode !== topMount.parentNode || topMount.nextSibling !== mount) {&lt;br /&gt;
            topMount.parentNode.insertBefore(mount, topMount.nextSibling);&lt;br /&gt;
        }&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fallbackGeometry(width) {&lt;br /&gt;
        var cut = Math.max(6, Math.min(10, Math.round((width / 5) * 0.045)));&lt;br /&gt;
        var run = Math.round(cut * ROW_HEIGHT / 22);&lt;br /&gt;
        var outer = [&lt;br /&gt;
            [Math.round(width * 0.2), 0],&lt;br /&gt;
            [Math.round(width * 0.8), 0],&lt;br /&gt;
            [Math.round(width * 0.8) - run, ROW_HEIGHT],&lt;br /&gt;
            [Math.round(width * 0.2) + run, ROW_HEIGHT]&lt;br /&gt;
        ];&lt;br /&gt;
        return {&lt;br /&gt;
            outer:outer,&lt;br /&gt;
            leftDirection:projectSidesToY(outer, 0, ROW_HEIGHT - 1, INSET_OFFSET).map(function (point, index) {&lt;br /&gt;
                return index === 0 || index === 3 ? point : null;&lt;br /&gt;
            }).filter(Boolean),&lt;br /&gt;
            rightDirection:projectSidesToY(outer, 0, ROW_HEIGHT - 1, INSET_OFFSET).slice(1, 3)&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readFrameGeometry(topMount, mount, width) {&lt;br /&gt;
        var topSvg = topMount.querySelector(&#039;.portal-category-svg&#039;);&lt;br /&gt;
        var settingsAnchor = topMount.querySelector(&#039;.portal-cat-anchor[data-category-key=&amp;quot;settings&amp;quot;]&#039;);&lt;br /&gt;
        var corporationAnchor = topMount.querySelector(&#039;.portal-cat-anchor[data-category-key=&amp;quot;corporations&amp;quot;]&#039;);&lt;br /&gt;
        var settings = settingsAnchor &amp;amp;&amp;amp; settingsAnchor.querySelector(&#039;.portal-cat-edge&#039;);&lt;br /&gt;
        var corporations = corporationAnchor &amp;amp;&amp;amp; corporationAnchor.querySelector(&#039;.portal-cat-edge&#039;);&lt;br /&gt;
        var settingsInset = settingsAnchor &amp;amp;&amp;amp; settingsAnchor.querySelector(&#039;.portal-cat-inset-left&#039;);&lt;br /&gt;
        var corporationInset = corporationAnchor &amp;amp;&amp;amp; corporationAnchor.querySelector(&#039;.portal-cat-inset-right&#039;);&lt;br /&gt;
        var settingsPoints = settings ? parsePoints(settings.getAttribute(&#039;points&#039;)) : [];&lt;br /&gt;
        var corporationPoints = corporations ? parsePoints(corporations.getAttribute(&#039;points&#039;)) : [];&lt;br /&gt;
        var settingsInsetPoints = settingsInset ? [&lt;br /&gt;
            [Number(settingsInset.getAttribute(&#039;x1&#039;)), Number(settingsInset.getAttribute(&#039;y1&#039;))],&lt;br /&gt;
            [Number(settingsInset.getAttribute(&#039;x2&#039;)), Number(settingsInset.getAttribute(&#039;y2&#039;))]&lt;br /&gt;
        ] : [];&lt;br /&gt;
        var corporationInsetPoints = corporationInset ? [&lt;br /&gt;
            [Number(corporationInset.getAttribute(&#039;x1&#039;)), Number(corporationInset.getAttribute(&#039;y1&#039;))],&lt;br /&gt;
            [Number(corporationInset.getAttribute(&#039;x2&#039;)), Number(corporationInset.getAttribute(&#039;y2&#039;))]&lt;br /&gt;
        ] : [];&lt;br /&gt;
        var svgRect;&lt;br /&gt;
        var mountRect;&lt;br /&gt;
        var viewBox;&lt;br /&gt;
        var scaleX;&lt;br /&gt;
        var scaleY;&lt;br /&gt;
        var outer;&lt;br /&gt;
        var fallback;&lt;br /&gt;
&lt;br /&gt;
        if (!topSvg || settingsPoints.length !== 4 || corporationPoints.length !== 4) {&lt;br /&gt;
            return fallbackGeometry(width);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        svgRect = topSvg.getBoundingClientRect();&lt;br /&gt;
        mountRect = mount.getBoundingClientRect();&lt;br /&gt;
        viewBox = getViewBox(topSvg);&lt;br /&gt;
        if (&lt;br /&gt;
            !viewBox ||&lt;br /&gt;
            !(svgRect.width &amp;gt; 0) ||&lt;br /&gt;
            !(svgRect.height &amp;gt; 0) ||&lt;br /&gt;
            !(mountRect.width &amp;gt; 0)&lt;br /&gt;
        ) {&lt;br /&gt;
            return fallbackGeometry(width);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        scaleX = svgRect.width / viewBox.width;&lt;br /&gt;
        scaleY = svgRect.height / viewBox.height;&lt;br /&gt;
&lt;br /&gt;
        function screenPoint(point) {&lt;br /&gt;
            return [&lt;br /&gt;
                svgRect.left + (point[0] - viewBox.x) * scaleX,&lt;br /&gt;
                svgRect.top + (point[1] - viewBox.y) * scaleY&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function localLineXAt(pointA, pointB, localY) {&lt;br /&gt;
            var a = screenPoint(pointA);&lt;br /&gt;
            var b = screenPoint(pointB);&lt;br /&gt;
            var targetY = mountRect.top + localY;&lt;br /&gt;
            var dy = b[1] - a[1];&lt;br /&gt;
            var screenX;&lt;br /&gt;
&lt;br /&gt;
            if (Math.abs(dy) &amp;lt; 0.000001) {&lt;br /&gt;
                return a[0] - mountRect.left;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            screenX = a[0] + (targetY - a[1]) * (b[0] - a[0]) / dy;&lt;br /&gt;
            return screenX - mountRect.left;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 설정 버튼의 왼쪽 사선과 기업 버튼의 오른쪽 사선을 화면 좌표계의&lt;br /&gt;
         * 직선으로 읽고, 새 프레임의 상단·하단 높이까지 그대로 연장한다.&lt;br /&gt;
         * 단순히 기존 x 편차를 재사용하지 않으므로 행 높이가 달라도 경사각이&lt;br /&gt;
         * 바뀌지 않으며 두 프레임 사이의 9px 겹침 안에서도 한 선으로 이어진다.&lt;br /&gt;
         */&lt;br /&gt;
        outer = [&lt;br /&gt;
            [localLineXAt(settingsPoints[0], settingsPoints[3], 0), 0],&lt;br /&gt;
            [localLineXAt(corporationPoints[1], corporationPoints[2], 0), 0],&lt;br /&gt;
            [localLineXAt(corporationPoints[1], corporationPoints[2], ROW_HEIGHT), ROW_HEIGHT],&lt;br /&gt;
            [localLineXAt(settingsPoints[0], settingsPoints[3], ROW_HEIGHT), ROW_HEIGHT]&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        fallback = {&lt;br /&gt;
            leftDirection:[&lt;br /&gt;
                [lineXAtY(outer[0], outer[3], 0) + INSET_OFFSET, 0],&lt;br /&gt;
                [lineXAtY(outer[0], outer[3], ROW_HEIGHT - 1) + INSET_OFFSET, ROW_HEIGHT - 1]&lt;br /&gt;
            ],&lt;br /&gt;
            rightDirection:[&lt;br /&gt;
                [lineXAtY(outer[1], outer[2], 0) - INSET_OFFSET, 0],&lt;br /&gt;
                [lineXAtY(outer[1], outer[2], ROW_HEIGHT - 1) - INSET_OFFSET, ROW_HEIGHT - 1]&lt;br /&gt;
            ]&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            outer:outer,&lt;br /&gt;
            leftDirection:settingsInsetPoints.length === 2 &amp;amp;&amp;amp; settingsInsetPoints.every(function (point) {&lt;br /&gt;
                return Number.isFinite(point[0]) &amp;amp;&amp;amp; Number.isFinite(point[1]);&lt;br /&gt;
            }) ? [&lt;br /&gt;
                [localLineXAt(settingsInsetPoints[0], settingsInsetPoints[1], 0), 0],&lt;br /&gt;
                [localLineXAt(settingsInsetPoints[0], settingsInsetPoints[1], ROW_HEIGHT - 1), ROW_HEIGHT - 1]&lt;br /&gt;
            ] : fallback.leftDirection,&lt;br /&gt;
            rightDirection:corporationInsetPoints.length === 2 &amp;amp;&amp;amp; corporationInsetPoints.every(function (point) {&lt;br /&gt;
                return Number.isFinite(point[0]) &amp;amp;&amp;amp; Number.isFinite(point[1]);&lt;br /&gt;
            }) ? [&lt;br /&gt;
                [localLineXAt(corporationInsetPoints[0], corporationInsetPoints[1], 0), 0],&lt;br /&gt;
                [localLineXAt(corporationInsetPoints[0], corporationInsetPoints[1], ROW_HEIGHT - 1), ROW_HEIGHT - 1]&lt;br /&gt;
            ] : fallback.rightDirection&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendFrame(svg, outer, directions) {&lt;br /&gt;
        var surface = projectSidesToY(outer, 0, ROW_HEIGHT - 1, OUTER_BORDER);&lt;br /&gt;
        var well = projectSidesToY(outer, TOP_FRAME, ROW_HEIGHT - BOTTOM_FRAME, TOP_FRAME);&lt;br /&gt;
        var outline = projectSidesToY(outer, 0.5, ROW_HEIGHT - 0.5, 0.5);&lt;br /&gt;
        var innerDirection = projectSidesToY(outer, 1.5, ROW_HEIGHT - 1.5, 1.5);&lt;br /&gt;
        var group = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var joinDepth = TOP_FRAME;&lt;br /&gt;
        var leftJoinCover;&lt;br /&gt;
        var rightJoinCover;&lt;br /&gt;
        var leftDirection = [&lt;br /&gt;
            innerDirection[0],&lt;br /&gt;
            innerDirection[3]&lt;br /&gt;
        ];&lt;br /&gt;
        var rightDirection = [&lt;br /&gt;
            innerDirection[1],&lt;br /&gt;
            innerDirection[2]&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        group.setAttribute(&#039;class&#039;, &#039;portal-section-frame&#039;);&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-frame-edge&#039;, outer));&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-frame-surface&#039;, surface));&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-frame-well&#039;, well));&lt;br /&gt;
&lt;br /&gt;
        /* 우물의 수평 방향성은 좌우 접합 처리와 무관하므로 먼저 그린다. */&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-well-shadow&#039;, well[0], well[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-well-highlight&#039;, well[2], well[3]));&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 기존 프레임과 포개지는 상단 9px은 같은 재료의 면이다.&lt;br /&gt;
         * 덮개는 외곽에서 우물 경계까지 정확히 채우되 우물 안으로 1px&lt;br /&gt;
         * 침범하지 않는다. 좌우 방향선은 덮개 뒤 y=9부터 직접 시작한다.&lt;br /&gt;
         */&lt;br /&gt;
        leftJoinCover = [&lt;br /&gt;
            [lineXAtY(outer[0], outer[3], 0) - OUTER_BORDER, 0],&lt;br /&gt;
            [lineXAtY(well[0], well[3], 0), 0],&lt;br /&gt;
            [lineXAtY(well[0], well[3], joinDepth), joinDepth],&lt;br /&gt;
            [lineXAtY(outer[0], outer[3], joinDepth) - OUTER_BORDER, joinDepth]&lt;br /&gt;
        ];&lt;br /&gt;
        rightJoinCover = [&lt;br /&gt;
            [lineXAtY(well[1], well[2], 0), 0],&lt;br /&gt;
            [lineXAtY(outer[1], outer[2], 0) + OUTER_BORDER, 0],&lt;br /&gt;
            [lineXAtY(outer[1], outer[2], joinDepth) + OUTER_BORDER, joinDepth],&lt;br /&gt;
            [lineXAtY(well[1], well[2], joinDepth), joinDepth]&lt;br /&gt;
        ];&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-join-side-cover&#039;, leftJoinCover));&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-join-side-cover&#039;, rightJoinCover));&lt;br /&gt;
&lt;br /&gt;
        /* #050505 외곽 1px과 그 안쪽 방향선 1px을 서로 다른 픽셀 열에 둔다. */&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-frame-outline&#039;, outline));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-frame-shadow&#039;, rightDirection[1], leftDirection[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-frame-shadow&#039;, leftDirection[1], leftDirection[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-frame-highlight&#039;, rightDirection[0], rightDirection[1]));&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-well-shadow&#039;, well[1], well[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-well-highlight&#039;, well[3], well[0]));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function splitButtons(outer) {&lt;br /&gt;
        var union = projectSidesToY(outer, BUTTON_TOP, BUTTON_BOTTOM, TOP_FRAME + GAP);&lt;br /&gt;
        var topMid = (union[0][0] + union[1][0]) / 2;&lt;br /&gt;
        var bottomMid = (union[3][0] + union[2][0]) / 2;&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            [&lt;br /&gt;
                union[0],&lt;br /&gt;
                [topMid - GAP / 2, BUTTON_TOP],&lt;br /&gt;
                [bottomMid - GAP / 2, BUTTON_BOTTOM],&lt;br /&gt;
                union[3]&lt;br /&gt;
            ],&lt;br /&gt;
            [&lt;br /&gt;
                [topMid + GAP / 2, BUTTON_TOP],&lt;br /&gt;
                union[1],&lt;br /&gt;
                union[2],&lt;br /&gt;
                [bottomMid + GAP / 2, BUTTON_BOTTOM]&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendButton(svg, item, points) {&lt;br /&gt;
        var anchor = createSvgElement(&#039;a&#039;);&lt;br /&gt;
        var geometry = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var surfacePoints = projectSidesToY(&lt;br /&gt;
            points,&lt;br /&gt;
            points[0][1] + OUTER_BORDER,&lt;br /&gt;
            points[3][1] - OUTER_BORDER,&lt;br /&gt;
            OUTER_BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var insetPoints = projectSidesToY(&lt;br /&gt;
            points,&lt;br /&gt;
            points[0][1] + INSET_OFFSET,&lt;br /&gt;
            points[3][1] - INSET_OFFSET,&lt;br /&gt;
            INSET_OFFSET&lt;br /&gt;
        );&lt;br /&gt;
        var center = getCentroid(surfacePoints);&lt;br /&gt;
        var text = createSvgElement(&#039;text&#039;);&lt;br /&gt;
        var hit = createPolygon(&#039;portal-cat-hit&#039;, points);&lt;br /&gt;
        var href = item.href || buildWikiPath(item.title);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;class&#039;, &#039;portal-cat-anchor portal-section-anchor&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, item.key);&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
&lt;br /&gt;
        geometry.setAttribute(&#039;class&#039;, &#039;portal-section-button-geometry&#039;);&lt;br /&gt;
        geometry.appendChild(createPolygon(&#039;portal-cat-edge&#039;, points));&lt;br /&gt;
        geometry.appendChild(createPolygon(&#039;portal-cat-surface&#039;, surfacePoints));&lt;br /&gt;
        geometry.appendChild(createLine(&#039;portal-cat-inset portal-cat-inset-top&#039;, insetPoints[0], insetPoints[1]));&lt;br /&gt;
        geometry.appendChild(createLine(&#039;portal-cat-inset portal-cat-inset-right&#039;, insetPoints[1], insetPoints[2]));&lt;br /&gt;
        geometry.appendChild(createLine(&#039;portal-cat-inset portal-cat-inset-bottom&#039;, insetPoints[2], insetPoints[3]));&lt;br /&gt;
        geometry.appendChild(createLine(&#039;portal-cat-inset portal-cat-inset-left&#039;, insetPoints[3], insetPoints[0]));&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
        geometry.appendChild(hit);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;class&#039;, &#039;portal-cat-label portal-section-label&#039;);&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, roundCoordinate(center[0]));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, roundCoordinate((BUTTON_TOP + BUTTON_BOTTOM) / 2 + 1));&lt;br /&gt;
        text.textContent = item.label;&lt;br /&gt;
&lt;br /&gt;
        anchor.appendChild(geometry);&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        svg.appendChild(anchor);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPortal(portal) {&lt;br /&gt;
        var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        var mount;&lt;br /&gt;
        var rect;&lt;br /&gt;
        var width;&lt;br /&gt;
        var geometry;&lt;br /&gt;
        var outer;&lt;br /&gt;
        var buttons;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var svg;&lt;br /&gt;
&lt;br /&gt;
        if (!topMount || !topMount.querySelector(&#039;.portal-category-svg&#039;)) return false;&lt;br /&gt;
        mount = ensureMount(portal, topMount);&lt;br /&gt;
        rect = mount.getBoundingClientRect();&lt;br /&gt;
        width = Math.floor(rect.width || topMount.getBoundingClientRect().width || 0);&lt;br /&gt;
        if (!width) return false;&lt;br /&gt;
&lt;br /&gt;
        geometry = readFrameGeometry(topMount, mount, width);&lt;br /&gt;
        outer = geometry.outer;&lt;br /&gt;
        buttons = splitButtons(outer);&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        svg = createSvgElement(&#039;svg&#039;);&lt;br /&gt;
&lt;br /&gt;
        nav.className = &#039;portal-section-nav&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, &#039;작품 영역&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;class&#039;, &#039;portal-section-svg&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + width + &#039; &#039; + ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        appendFrame(svg, outer, geometry);&lt;br /&gt;
        ITEMS.forEach(function (item, index) {&lt;br /&gt;
            appendButton(svg, item, buttons[index]);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        nav.appendChild(svg);&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-width&#039;, String(width));&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectPortals(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var portals = [];&lt;br /&gt;
&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)) portals.push(scope);&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;.main-portal&#039;), function (portal) {&lt;br /&gt;
            if (portals.indexOf(portal) === -1) portals.push(portal);&lt;br /&gt;
        });&lt;br /&gt;
        return portals;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root) {&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
        collectPortals(root).forEach(function (portal) {&lt;br /&gt;
            if (renderPortal(portal)) rendered += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleRender(root) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function observe() {&lt;br /&gt;
        if (observer || !window.MutationObserver || !document.documentElement) return;&lt;br /&gt;
        observer = new window.MutationObserver(function (records) {&lt;br /&gt;
            var relevant = records.some(function (record) {&lt;br /&gt;
                return Array.prototype.some.call(record.addedNodes || [], function (node) {&lt;br /&gt;
                    return node &amp;amp;&amp;amp; node.nodeType === 1 &amp;amp;&amp;amp; (&lt;br /&gt;
                        (node.matches &amp;amp;&amp;amp; node.matches(&#039;.portal-category-svg&#039;)) ||&lt;br /&gt;
                        (node.querySelector &amp;amp;&amp;amp; node.querySelector(&#039;.portal-category-svg&#039;))&lt;br /&gt;
                    );&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
            if (relevant) scheduleRender(document);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(document.documentElement, { childList:true, subtree:true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function init(root) {&lt;br /&gt;
        renderAll(root || document);&lt;br /&gt;
        observe();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            scheduleRender(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { init(document); });&lt;br /&gt;
    } else {&lt;br /&gt;
        init(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        window.clearTimeout(resizeTimer);&lt;br /&gt;
        resizeTimer = window.setTimeout(function () {&lt;br /&gt;
            resizeTimer = 0;&lt;br /&gt;
            renderAll(document);&lt;br /&gt;
        }, 80);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
window.PortalSectionNav = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        init:init,&lt;br /&gt;
        render:renderPortal,&lt;br /&gt;
        renderAll:renderAll&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4660</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4660"/>
		<updated>2026-07-17T00:52:09Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-bottom-visible-gap-2px-20260717 / MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:36px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
설정 왼쪽부터 기업 및 공동체 오른쪽까지의 실제 SVG 경계를 읽어&lt;br /&gt;
중앙 프레임의 최외곽 폭으로 사용한다. 좌우 투명 영역은 새 프레임으로&lt;br /&gt;
채우지 않는다. 형상은 위가 넓고 아래가 좁아지도록 뒤집고, 기존 상단&lt;br /&gt;
프레임 하단 9px 안으로 겹쳐 하나로 접합한다.&lt;br /&gt;
좌우 경사는 기존 설정·기업 버튼 사선의 화면상 기울기를 그대로 연장한다.&lt;br /&gt;
외곽·프레임 면·우물·버튼의 x 좌표도 같은 직선식으로 투영해 전 층의&lt;br /&gt;
사선이 서로 평행하고 끊김 없이 이어지게 한다.&lt;br /&gt;
두 프레임이 실제로 포개지는 상단 8px에서는 #050505를 #1d1d1d 면으로&lt;br /&gt;
덮는다. 좌우 방향선은 외곽선에서 재계산하지 않고 기존 설정 버튼의 좌측&lt;br /&gt;
인셋선과 기업 버튼의 우측 인셋선을 직접 읽어 같은 직선으로 연장한다.&lt;br /&gt;
&lt;br /&gt;
초기 길라잡이 규격:&lt;br /&gt;
- 버튼 외곽 22px&lt;br /&gt;
- 프레임 실색 #1d1d1d 7px&lt;br /&gt;
- 우물 간격 2px&lt;br /&gt;
- 전체 부착 행 43px&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 43px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:43px !important;&lt;br /&gt;
    min-height:43px !important;&lt;br /&gt;
    max-height:43px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:43px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-outline {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:10%;&lt;br /&gt;
    right:auto;&lt;br /&gt;
    width:80%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-svg {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 42px 본체·광원 + 상하 1px #050505 외곽선으로 44px이다.&lt;br /&gt;
   상하 2px #080808 간격, 상단 9px·하단 8px 프레임을 포함한 전체 행은 65px이다.&lt;br /&gt;
   상단 9px 중 외곽선·광원선을 제외한 #1d1d1d 실색 영역은 7px이다. */&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단 총 36px:&lt;br /&gt;
4px 프레임 + 2px #080808 + 24px 버튼 + 2px #080808 + 4px 프레임&lt;br /&gt;
&lt;br /&gt;
하단 총 65px:&lt;br /&gt;
중앙에서 위로 솟은 패널 하단 프레임 + 2px #080808&lt;br /&gt;
+ 44px 길라잡이 외곽 + 2px #080808 + 공유 하단 프레임&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:24px !important;&lt;br /&gt;
    min-height:24px !important;&lt;br /&gt;
    max-height:24px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:22px !important;&lt;br /&gt;
    min-height:22px !important;&lt;br /&gt;
    max-height:22px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4659</id>
		<title>미디어위키:Common.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4659"/>
		<updated>2026-07-17T00:52:09Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-bottom-visible-gap-2px-20260717 / Common.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;mw.loader.load(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* Raw MediaWiki script cache key.&lt;br /&gt;
   This is not a semantic version. It only prevents stale raw JS during active editing. */&lt;br /&gt;
window.CLBI_RAW_LOAD_BUST = &#039;portal-section-bottom-gap-two-20260717-045&#039;;&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Raw script loads are tracked so the site boot gate can wait for real subsystem scripts&lt;br /&gt;
before releasing the normal surface.  The old prefixed load-bust variable remains for&lt;br /&gt;
compatibility only; new loader state uses unprefixed EntryScriptLoads/EntryRawScriptPromises.&lt;br /&gt;
*/&lt;br /&gt;
window.EntryScriptLoads = window.EntryScriptLoads || [];&lt;br /&gt;
window.EntryRawScriptPromises = window.EntryRawScriptPromises || {};&lt;br /&gt;
function buildEntryRawScriptUrl(key) {&lt;br /&gt;
    var url = &#039;/index.php?title=&#039; + encodeURIComponent(key) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;;&lt;br /&gt;
    var token;&lt;br /&gt;
    var sep;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, key);&lt;br /&gt;
        }&lt;br /&gt;
        if (/[?&amp;amp;]_entryRev=/.test(url)) return url;&lt;br /&gt;
        token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(&#039;raw-script-fallback&#039;) : window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        token = window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
    return url + sep + &#039;v=&#039; + encodeURIComponent(String(token || &#039;entry-raw&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadClbiRawScript(title) {&lt;br /&gt;
    var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
    var promise;&lt;br /&gt;
&lt;br /&gt;
    if (!key) return Promise.resolve(false);&lt;br /&gt;
    if (window.EntryRawScriptPromises[key]) return window.EntryRawScriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
    promise = Promise.resolve()&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.ensureLoaded === &#039;function&#039;) {&lt;br /&gt;
                return window.RevisionManifest.ensureLoaded();&lt;br /&gt;
            }&lt;br /&gt;
            return null;&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function () { return null; })&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
                script.src = buildEntryRawScriptUrl(key);&lt;br /&gt;
                script.async = true;&lt;br /&gt;
                script.setAttribute(&#039;data-entry-raw-script&#039;, key);&lt;br /&gt;
                script.onload = function () { resolve({ title: key, ok: true, src: script.src }); };&lt;br /&gt;
                script.onerror = function () { resolve({ title: key, ok: false, src: script.src }); };&lt;br /&gt;
                (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    window.EntryRawScriptPromises[key] = promise;&lt;br /&gt;
    window.EntryScriptLoads.push(promise);&lt;br /&gt;
    return promise;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Main-page fresh assets with fallback&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css의 MainPage.css import는 안전망으로 그대로 유지한다.&lt;br /&gt;
대문에서는 새로고침마다 timestamp가 붙은 CSS/JS를 추가로 불러와&lt;br /&gt;
브라우저·프록시의 오래된 raw 응답을 우회한다.&lt;br /&gt;
&lt;br /&gt;
이 보조 로더가 실패해도 기존 정적 import와 기존 화면은 남는다.&lt;br /&gt;
*/&lt;br /&gt;
window.MainPageFreshAssets = window.MainPageFreshAssets || (function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var sessionToken = String(Date.now());&lt;br /&gt;
    var ASSET_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var scriptPromises = {};&lt;br /&gt;
    var styleNode = null;&lt;br /&gt;
&lt;br /&gt;
    function isMainPage() {&lt;br /&gt;
        var page = &#039;&#039;;&lt;br /&gt;
        try {&lt;br /&gt;
            page = String(mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;);&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return page.replace(/_/g, &#039; &#039;) === &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrl(title, type) {&lt;br /&gt;
        var ctype = type === &#039;style&#039; ? &#039;text/css&#039; : &#039;text/javascript&#039;;&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) +&lt;br /&gt;
            &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype) +&lt;br /&gt;
            &#039;&amp;amp;_mainPageFresh=&#039; + encodeURIComponent(sessionToken);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadStyle() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        if (styleNode &amp;amp;&amp;amp; styleNode.parentNode) {&lt;br /&gt;
            return Promise.resolve(true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var link = document.createElement(&#039;link&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            link.rel = &#039;stylesheet&#039;;&lt;br /&gt;
            link.href = rawUrl(&#039;MediaWiki:MainPage.css&#039;, &#039;style&#039;);&lt;br /&gt;
            link.setAttribute(&#039;data-main-page-fresh-style&#039;, sessionToken);&lt;br /&gt;
            link.onload = function () {&lt;br /&gt;
                styleNode = link;&lt;br /&gt;
                settle(true);&lt;br /&gt;
            };&lt;br /&gt;
            link.onerror = function () {&lt;br /&gt;
                /* Common.css의 정적 import가 그대로 남아 있으므로 화면은 유지된다. */&lt;br /&gt;
                settle(false);&lt;br /&gt;
            };&lt;br /&gt;
            timeout = window.setTimeout(function () { settle(false); }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(link);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadScript(title) {&lt;br /&gt;
        var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!key || !isMainPage()) return Promise.resolve(false);&lt;br /&gt;
        if (scriptPromises[key]) return scriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
        scriptPromises[key] = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            script.src = rawUrl(key, &#039;script&#039;);&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-main-page-fresh-script&#039;, key);&lt;br /&gt;
            script.onload = function () { settle(true); };&lt;br /&gt;
            script.onerror = function () { settle(false); };&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return scriptPromises[key];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensure() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        return loadStyle()&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryPillar.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:PortalSectionNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:BottomGuideNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (window.CategoryNav &amp;amp;&amp;amp; typeof window.CategoryNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.CategoryPillar &amp;amp;&amp;amp; typeof window.CategoryPillar.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryPillar.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.PortalSectionNav &amp;amp;&amp;amp; typeof window.PortalSectionNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.PortalSectionNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.render === &#039;function&#039;) {&lt;br /&gt;
                    window.BottomGuideNav.render();&lt;br /&gt;
                }&lt;br /&gt;
                return true;&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function status() {&lt;br /&gt;
        return {&lt;br /&gt;
            mainPage: isMainPage(),&lt;br /&gt;
            token: sessionToken,&lt;br /&gt;
            style: !!document.querySelector(&#039;link[data-main-page-fresh-style]&#039;),&lt;br /&gt;
            category: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            pillar: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryPillar.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            section: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:PortalSectionNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            guide: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:BottomGuideNav.js&amp;quot;]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            ensure();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(ensure, 0);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        ensure: ensure,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Account access page marker&lt;br /&gt;
   =========================================&lt;br /&gt;
   New shared systems use unprefixed names.  The server body class differs&lt;br /&gt;
   between skins/locales, so the canonical special-page name is normalized&lt;br /&gt;
   once and exposed as a stable page-state class for the login surface.&lt;br /&gt;
*/&lt;br /&gt;
(function markAccountLoginPage(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var canonical = &#039;&#039;;&lt;br /&gt;
    var pageName = &#039;&#039;;&lt;br /&gt;
    var isLoginPage = false;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        canonical = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039; : &#039;&#039;).toLowerCase();&lt;br /&gt;
        pageName = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    isLoginPage = canonical === &#039;userlogin&#039; || /^(?:special|특수):(?:userlogin|로그인)$/.test(pageName);&lt;br /&gt;
    if (!isLoginPage) return;&lt;br /&gt;
&lt;br /&gt;
    document.documentElement.classList.add(&#039;account-login-page-root&#039;);&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountTarget(link) {&lt;br /&gt;
        var href = &#039;&#039;;&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var text = &#039;&#039;;&lt;br /&gt;
        var sample = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!link) return false;&lt;br /&gt;
        href = String(link.getAttribute(&#039;href&#039;) || &#039;&#039;);&lt;br /&gt;
        title = String(link.getAttribute(&#039;title&#039;) || &#039;&#039;);&lt;br /&gt;
        text = String(link.textContent || &#039;&#039;);&lt;br /&gt;
        try { href = decodeURIComponent(href); } catch (err) {}&lt;br /&gt;
        sample = (href + &#039; &#039; + title + &#039; &#039; + text).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        return /(?:special|특수)\s*[:%]\s*(?:createaccount|계정\s*(?:만들기|생성))|createaccount|계정\s*(?:만들기|생성)/i.test(sample);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function suppressCreateAccountSurface(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var fixedSelectors = [&lt;br /&gt;
            &#039;.mw-createacct-benefits-container&#039;,&lt;br /&gt;
            &#039;.mw-createacct-benefits-list&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-cta&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;#mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;.mw-userlogin-create&#039;&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        fixedSelectors.forEach(function (selector) {&lt;br /&gt;
            Array.prototype.forEach.call(scope.querySelectorAll(selector), function (node) {&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                node.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;a[href]&#039;), function (link) {&lt;br /&gt;
            var container;&lt;br /&gt;
            if (!isCreateAccountTarget(link)) return;&lt;br /&gt;
&lt;br /&gt;
            link.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            link.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            link.setAttribute(&#039;tabindex&#039;, &#039;-1&#039;);&lt;br /&gt;
&lt;br /&gt;
            container = link.closest(&#039;.mw-ui-vform-field, .oo-ui-fieldLayout, .mw-userlogin-create, p, li&#039;);&lt;br /&gt;
            if (container &amp;amp;&amp;amp; container.querySelectorAll(&#039;a&#039;).length === 1) {&lt;br /&gt;
                container.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                container.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyMarker() {&lt;br /&gt;
        if (!document.body) return;&lt;br /&gt;
        document.body.classList.add(&#039;account-login-page&#039;);&lt;br /&gt;
        suppressCreateAccountSurface(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, applyMarker, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyMarker();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                suppressCreateAccountSurface(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Boot gate prelude&lt;br /&gt;
   =========================================&lt;br /&gt;
   This tiny prelude runs before the full EntryStore/EntryLoader/BootGate implementation.&lt;br /&gt;
   Its job is only to guarantee that the user sees the separate loading surface before the&lt;br /&gt;
   normal wiki shell can paint.  The full BootGate later adopts the same DOM node and keeps&lt;br /&gt;
   it open until the real full-entry readiness contract is satisfied.&lt;br /&gt;
&lt;br /&gt;
   Maintenance rule:&lt;br /&gt;
   - Do not move first-screen hiding into a late subsystem callback.  If the boot gate is&lt;br /&gt;
     meant to protect the first impression, the surface must be activated before sidebars,&lt;br /&gt;
     navbars, document wells, nations panels, or other entry surfaces can visibly fill in.&lt;br /&gt;
   - New names remain unprefixed.  Existing prefixed names elsewhere are legacy aliases only.&lt;br /&gt;
*/&lt;br /&gt;
(function installBootGatePrelude(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SCREEN_ID = &#039;boot-gate-screen&#039;;&lt;br /&gt;
    var STYLE_ID = &#039;boot-gate-prelude-style&#039;;&lt;br /&gt;
    var START_TIME = Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getMwConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.mw &amp;amp;&amp;amp; window.mw.config &amp;amp;&amp;amp; typeof window.mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = window.mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDeveloperOrEditingPage() {&lt;br /&gt;
        var ns = Number(getMwConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(getMwConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(getMwConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Developer/system pages must stay practical.  Editing MediaWiki:, File:,&lt;br /&gt;
         * Project:, Template:, Module:, Category:, or any non-view action already forces&lt;br /&gt;
         * a full page load in MediaWiki, so showing the entry boot screen there only slows&lt;br /&gt;
         * maintenance work and does not improve the public first impression.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = getMwConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(getMwConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(getMwConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var anonymousUser = isAnonymousUser();&lt;br /&gt;
    var disabled = isAuthenticationPage() || (!anonymousUser &amp;amp;&amp;amp; (hasQueryFlag(&#039;bootGate&#039;, &#039;0&#039;) || isDeveloperOrEditingPage()));&lt;br /&gt;
&lt;br /&gt;
    function injectStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (disabled || document.getElementById(STYLE_ID)) return;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = STYLE_ID;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active,body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt;:not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen{position:fixed!important;inset:0!important;z-index:2147483000!important;display:flex!important;align-items:center!important;justify-content:center!important;background:#080808!important;color:#d8d8d8!important;opacity:1;pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-decoration-layer{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-panel{position:relative;z-index:2;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-close{display:none;position:absolute;right:7px;top:6px;z-index:3;height:18px;min-width:18px;border:1px solid #000;background:#141414;color:#ddd;font-size:11px;line-height:16px;padding:0 5px;cursor:pointer;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview{inset:auto!important;left:18px!important;top:18px!important;width:min(720px,calc(100vw - 380px))!important;height:360px!important;z-index:99990!important;overflow:hidden!important;border:1px solid #000!important;box-shadow:0 10px 28px rgba(0,0,0,.55)!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-panel,#boot-gate-screen.is-preview .boot-gate-close{pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-close{display:block;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activate() {&lt;br /&gt;
        if (disabled) return;&lt;br /&gt;
        injectStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeNode() {&lt;br /&gt;
        var node;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var status;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var fill;&lt;br /&gt;
        var progress;&lt;br /&gt;
        var detail;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
&lt;br /&gt;
        if (disabled || !document.body) return null;&lt;br /&gt;
        activate();&lt;br /&gt;
&lt;br /&gt;
        node = document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) return node;&lt;br /&gt;
&lt;br /&gt;
        node = document.createElement(&#039;div&#039;);&lt;br /&gt;
        node.id = SCREEN_ID;&lt;br /&gt;
        node.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
        node.setAttribute(&#039;data-boot-gate-prelude&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        title.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        status = document.createElement(&#039;div&#039;);&lt;br /&gt;
        status.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        status.textContent = &#039;Preparing site entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        fill = document.createElement(&#039;div&#039;);&lt;br /&gt;
        fill.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(fill);&lt;br /&gt;
&lt;br /&gt;
        progress = document.createElement(&#039;div&#039;);&lt;br /&gt;
        progress.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        progress.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        detail = document.createElement(&#039;div&#039;);&lt;br /&gt;
        detail.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        detail.textContent = &#039;boot gate prelude&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { release(node); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(title);&lt;br /&gt;
        panel.appendChild(status);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(progress);&lt;br /&gt;
        panel.appendChild(detail);&lt;br /&gt;
        node.appendChild(decoLayer);&lt;br /&gt;
        node.appendChild(panel);&lt;br /&gt;
        node.appendChild(close);&lt;br /&gt;
&lt;br /&gt;
        document.body.insertBefore(node, document.body.firstChild || null);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function onBody(callback) {&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            callback();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, callback, { once: true });&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function tick() {&lt;br /&gt;
            if (document.body) return callback();&lt;br /&gt;
            window.setTimeout(tick, 10);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function release(node) {&lt;br /&gt;
        node = node || document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) {&lt;br /&gt;
            node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
            node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            }, 240);&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (document.documentElement) document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    activate();&lt;br /&gt;
    onBody(function () {&lt;br /&gt;
        activate();&lt;br /&gt;
        makeNode();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.__BootGatePrelude = {&lt;br /&gt;
        startTime: START_TIME,&lt;br /&gt;
        activate: activate,&lt;br /&gt;
        ensure: makeNode,&lt;br /&gt;
        release: release,&lt;br /&gt;
        node: function () { return document.getElementById(SCREEN_ID); },&lt;br /&gt;
        disabled: disabled&lt;br /&gt;
    };&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Site boot gate and entry artifact contract&lt;br /&gt;
   =========================================&lt;br /&gt;
   This is the initial-load full/half entry artifact system.&lt;br /&gt;
&lt;br /&gt;
   Purpose:&lt;br /&gt;
   - The loading screen is not decorative. It exists only during the first site entry in a&lt;br /&gt;
     tab, before the user is allowed into the normal wiki surface.&lt;br /&gt;
   - A &amp;quot;full&amp;quot; entry must mean that the first view of that system can appear without an&lt;br /&gt;
     additional visible data load. For the nations system, the 1950 entry is full only when&lt;br /&gt;
     its nation list/link-map data and first-view pixel decorations are ready in this tab.&lt;br /&gt;
   - A &amp;quot;half&amp;quot; entry is a predictive warm state for the next likely path. It may fetch and&lt;br /&gt;
     parse data, but may skip expensive final work such as image/canvas preparation until it&lt;br /&gt;
     is promoted to full.&lt;br /&gt;
   - This principle is a site-wide design priority, like SPA continuity. New viewers,&lt;br /&gt;
     document systems, and information panels should define their entry full/half packs&lt;br /&gt;
     before exposing a first screen that can visibly fill in later.&lt;br /&gt;
   - SPA navigation is a consumer phase, not a blocking phase.  Do not add BootGate holds&lt;br /&gt;
     to SPA route changes.  If a route needs seamless first paint, prepare its artifacts&lt;br /&gt;
     during the initial boot pack and have the subsystem consume EntryStore synchronously&lt;br /&gt;
     before inserting or painting visible DOM.&lt;br /&gt;
&lt;br /&gt;
   Naming rule:&lt;br /&gt;
   - New public APIs, globals, classes, functions, files, and components must not use a&lt;br /&gt;
     project prefix. Old prefixed globals are legacy compatibility surfaces only.&lt;br /&gt;
   - New code should use names such as BootGate, EntryLoader, EntryStore, and boot-gate-*.&lt;br /&gt;
*/&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var MANIFEST_TITLE = &#039;MediaWiki:EntryManifest.json&#039;;&lt;br /&gt;
    var BUILD_ID = &#039;20260711-existing-account-login-001&#039;;&lt;br /&gt;
    var deferredEntryWarmups = [];&lt;br /&gt;
    var READY_KEY = &#039;boot-gate-ready-version&#039;;&lt;br /&gt;
    var DISMISS_PARAM = &#039;bootGate&#039;;&lt;br /&gt;
    var bootStarted = false;&lt;br /&gt;
    var bootPromise = null;&lt;br /&gt;
    var bootNode = null;&lt;br /&gt;
    var bootStatusNode = null;&lt;br /&gt;
    var bootProgressNode = null;&lt;br /&gt;
    var bootDetailNode = null;&lt;br /&gt;
    var bootFillNode = null;&lt;br /&gt;
    var bootStartTime = 0;&lt;br /&gt;
    var earlyBootStyleInjected = false;&lt;br /&gt;
    var loginGateLocked = false;&lt;br /&gt;
    var loginGateActionNode = null;&lt;br /&gt;
&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.config &amp;amp;&amp;amp; typeof mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBootPageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        return canonical === &#039;createaccount&#039; || /^(?:special|특수):(?:createaccount|계정 ?(?:만들기|생성))/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = readConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(readConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requiresLoginGate() {&lt;br /&gt;
        return isAnonymousUser() &amp;amp;&amp;amp; !isAuthenticationPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBootExcludedPage() {&lt;br /&gt;
        var ns = Number(readConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(readConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(readConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * The public boot gate is for normal reading surfaces.  Developer/system&lt;br /&gt;
         * namespaces and edit/diff/history actions already reload outside SPA, so&lt;br /&gt;
         * blocking them would turn every maintenance save into another entry boot.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var LOGIN_REQUIRED = requiresLoginGate();&lt;br /&gt;
    var BOOT_EXCLUDED_PAGE = isBootExcludedPage() &amp;amp;&amp;amp; !LOGIN_REQUIRED;&lt;br /&gt;
&lt;br /&gt;
    function injectEarlyBootStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (earlyBootStyleInjected || !document.documentElement) return;&lt;br /&gt;
        earlyBootStyleInjected = true;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = &#039;boot-gate-early-style&#039;;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active, body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt; :not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBootSurface() {&lt;br /&gt;
        injectEarlyBootStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!BOOT_EXCLUDED_PAGE) activateBootSurface();&lt;br /&gt;
    else if (window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.release) window.__BootGatePrelude.release();&lt;br /&gt;
&lt;br /&gt;
    var defaultManifest = {&lt;br /&gt;
        version: &#039;20260710-globe-vhs-restore-entry-001&#039;,&lt;br /&gt;
        boot: {&lt;br /&gt;
            minDisplayMs: 950,&lt;br /&gt;
            cachedMinDisplayMs: 350,&lt;br /&gt;
            maxBlockingMs: 15000&lt;br /&gt;
        },&lt;br /&gt;
        initial: {&lt;br /&gt;
            full: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;decorations-registry&#039;,&lt;br /&gt;
                    label: &#039;DECORATION REGISTRY&#039;,&lt;br /&gt;
                    type: &#039;decorations&#039;,&lt;br /&gt;
                    ref: &#039;MediaWiki:Decorations.json&#039;,&lt;br /&gt;
                    page: &#039;시대&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    preparePixels: true&lt;br /&gt;
                },&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1950-entry&#039;,&lt;br /&gt;
                    label: &#039;1950 ERA ENTRY&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    level: &#039;full&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ],&lt;br /&gt;
            half: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1960-half&#039;,&lt;br /&gt;
                    label: &#039;1960 ERA HALF&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1960&#039;,&lt;br /&gt;
                    level: &#039;half&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ]&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function now() {&lt;br /&gt;
        return Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var BootPerf = window.BootPerf = window.BootPerf || (function () {&lt;br /&gt;
        var t0 = now();&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;entry&#039;), start: now(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = now();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = ended - item.start;&lt;br /&gt;
            item.offset = item.start - t0;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            entries.push(item);&lt;br /&gt;
            return item;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function instant(name, meta) {&lt;br /&gt;
            var t = now();&lt;br /&gt;
            entries.push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: t - t0, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measure(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function rows() {&lt;br /&gt;
            return entries.slice().sort(function (a, b) { return a.start - b.start; }).map(function (item) {&lt;br /&gt;
                return {&lt;br /&gt;
                    offset: item.offset,&lt;br /&gt;
                    ms: item.ms,&lt;br /&gt;
                    name: item.name,&lt;br /&gt;
                    meta: item.meta || {}&lt;br /&gt;
                };&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            var total = list.reduce(function (max, item) { return Math.max(max, item.offset + item.ms); }, 0);&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD_ID,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: total,&lt;br /&gt;
                entries: list,&lt;br /&gt;
                active: Object.keys(active).map(function (id) { return active[id]; })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function print() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            if (!window.console || !console.log) return summary();&lt;br /&gt;
            try {&lt;br /&gt;
                console.groupCollapsed(&#039;[BootPerf] entry loading timeline · &#039; + BUILD_ID);&lt;br /&gt;
                if (console.table) console.table(list.map(function (item) {&lt;br /&gt;
                    return {&lt;br /&gt;
                        offset: item.offset + &#039;ms&#039;,&lt;br /&gt;
                        duration: item.ms + &#039;ms&#039;,&lt;br /&gt;
                        name: item.name,&lt;br /&gt;
                        detail: JSON.stringify(item.meta || {})&lt;br /&gt;
                    };&lt;br /&gt;
                }));&lt;br /&gt;
                else list.forEach(function (item) { console.log(item.offset + &#039;ms&#039;, item.ms + &#039;ms&#039;, item.name, item.meta || {}); });&lt;br /&gt;
                console.groupEnd();&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            return summary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: instant,&lt;br /&gt;
            measure: measure,&lt;br /&gt;
            rows: rows,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            print: print&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var InteractionPerf = window.InteractionPerf = window.InteractionPerf || (function () {&lt;br /&gt;
        var BUILD = &#039;20260710-interaction-perf-instrument-001&#039;;&lt;br /&gt;
        var t0 = (window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now());&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
        var maxEntries = 1600;&lt;br /&gt;
&lt;br /&gt;
        function perfNow() {&lt;br /&gt;
            return window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function push(entry) {&lt;br /&gt;
            entries.push(entry);&lt;br /&gt;
            if (entries.length &amp;gt; maxEntries) entries.splice(0, entries.length - maxEntries);&lt;br /&gt;
            return entry;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;interaction&#039;), start: perfNow(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = perfNow();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = Math.round((ended - item.start) * 100) / 100;&lt;br /&gt;
            item.offset = Math.round((item.start - t0) * 100) / 100;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            return push(item);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function mark(name, meta) {&lt;br /&gt;
            var t = perfNow();&lt;br /&gt;
            return push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: Math.round((t - t0) * 100) / 100, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureSync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                var value = fn();&lt;br /&gt;
                end(id, { ok: true });&lt;br /&gt;
                return value;&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                throw err;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureAsync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: Math.round((perfNow() - t0) * 100) / 100,&lt;br /&gt;
                entries: entries.slice().sort(function (a, b) {&lt;br /&gt;
                    if (a.offset !== b.offset) return a.offset - b.offset;&lt;br /&gt;
                    return b.ms - a.ms;&lt;br /&gt;
                }),&lt;br /&gt;
                active: Object.keys(active).map(function (id) {&lt;br /&gt;
                    var item = active[id];&lt;br /&gt;
                    return { offset: Math.round((item.start - t0) * 100) / 100, ms: Math.round((perfNow() - item.start) * 100) / 100, name: item.name, meta: item.meta || {} };&lt;br /&gt;
                })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function table() {&lt;br /&gt;
            var data = summary().entries.map(function (entry) {&lt;br /&gt;
                return { offset: entry.offset, ms: entry.ms, name: entry.name, meta: entry.meta };&lt;br /&gt;
            });&lt;br /&gt;
            if (window.console &amp;amp;&amp;amp; console.table) console.table(data);&lt;br /&gt;
            return data;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.PerformanceObserver &amp;amp;&amp;amp; !window.CLBI_InteractionLongTaskObserverBound) {&lt;br /&gt;
                window.CLBI_InteractionLongTaskObserverBound = true;&lt;br /&gt;
                new PerformanceObserver(function (list) {&lt;br /&gt;
                    list.getEntries().forEach(function (entry) {&lt;br /&gt;
                        mark(&#039;browser long task&#039;, {&lt;br /&gt;
                            ms: Math.round(entry.duration * 100) / 100,&lt;br /&gt;
                            start: Math.round(entry.startTime * 100) / 100,&lt;br /&gt;
                            attribution: entry.attribution &amp;amp;&amp;amp; entry.attribution.length ? entry.attribution.length : 0&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }).observe({ entryTypes: [&#039;longtask&#039;] });&lt;br /&gt;
            }&lt;br /&gt;
        } catch (ignoreLongTaskObserver) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD,&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: mark,&lt;br /&gt;
            measureSync: measureSync,&lt;br /&gt;
            measureAsync: measureAsync,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            table: table&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
    function toArray(value) {&lt;br /&gt;
        return Array.prototype.slice.call(value || []);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function unique(list) {&lt;br /&gt;
        var seen = {};&lt;br /&gt;
        var out = [];&lt;br /&gt;
        (list || []).forEach(function (item) {&lt;br /&gt;
            item = String(item || &#039;&#039;).trim();&lt;br /&gt;
            if (!item || seen[item]) return;&lt;br /&gt;
            seen[item] = true;&lt;br /&gt;
            out.push(item);&lt;br /&gt;
        });&lt;br /&gt;
        return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasBootParam(value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + DISMISS_PARAM + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeTitle(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;#&#039;)[0]&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function extractTitleFromUrl(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;);&lt;br /&gt;
        var match = text.match(/[?&amp;amp;]title=([^&amp;amp;]+)/i);&lt;br /&gt;
        if (match) return normalizeTitle(decodeURIComponent(match[1].replace(/\+/g, &#039; &#039;)));&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeRefKey(ref) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        title = extractTitleFromUrl(text);&lt;br /&gt;
        if (title) return &#039;title:&#039; + title.toLowerCase();&lt;br /&gt;
        if (text.indexOf(&#039;/&#039;) === -1 &amp;amp;&amp;amp; text.indexOf(&#039;:&#039;) !== -1) return &#039;title:&#039; + normalizeTitle(text).toLowerCase();&lt;br /&gt;
        return &#039;url:&#039; + text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrlForRef(ref, ctype) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(text) || text.charAt(0) === &#039;/&#039;) return text;&lt;br /&gt;
        title = normalizeTitle(text.indexOf(&#039;:&#039;) !== -1 ? text : (&#039;MediaWiki:&#039; + text));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            return mw.util.getUrl(title, { action: &#039;raw&#039;, ctype: ctype || &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype || &#039;application/json&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getApiEndpoint() {&lt;br /&gt;
        return (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.wikiScript === &#039;function&#039;) ? mw.util.wikiScript(&#039;api&#039;) : &#039;/api.php&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchApi(params) {&lt;br /&gt;
        var body = new URLSearchParams();&lt;br /&gt;
        Object.keys(params || {}).forEach(function (key) {&lt;br /&gt;
            body.append(key, params[key]);&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(getApiEndpoint(), {&lt;br /&gt;
            method: &#039;POST&#039;,&lt;br /&gt;
            credentials: &#039;same-origin&#039;,&lt;br /&gt;
            headers: { &#039;Content-Type&#039;: &#039;application/x-www-form-urlencoded; charset=UTF-8&#039; },&lt;br /&gt;
            body: body.toString()&lt;br /&gt;
        }).then(function (res) {&lt;br /&gt;
            if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
            return res.json();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =========================================&lt;br /&gt;
       Revision manifest and persistent entry cache&lt;br /&gt;
       =========================================&lt;br /&gt;
       The first boot still prepares current-tab artifacts, but freshness is not guessed from&lt;br /&gt;
       filenames or old localStorage flags.  The client reads a tiny server-side current-state&lt;br /&gt;
       manifest, compares page revisions / file sha1 values, and only keeps cached raw resources&lt;br /&gt;
       whose revision token still matches the server.  The manifest is a latest-state table, not&lt;br /&gt;
       an append-only client log.&lt;br /&gt;
    */&lt;br /&gt;
    var REVISION_MANIFEST_ACTION = &#039;entryrevisionmanifest&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_LOCAL_KEY = &#039;entry-revision-manifest-current-v1&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_PRUNE_LOCAL_KEY = &#039;entry-revision-manifest-last-client-prune-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_NAME = &#039;entry-cache-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_INDEX_KEY = &#039;entry-cache-index-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_REQUEST_PREFIX = &#039;/__entry-cache__/&#039;;&lt;br /&gt;
    var ENTRY_CACHE_PACK_KEY = &#039;entry-cache-pack-state-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
    function hasCacheStorage() {&lt;br /&gt;
        return !!(window.caches &amp;amp;&amp;amp; typeof window.caches.open === &#039;function&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function safeJsonParse(text, fallback) {&lt;br /&gt;
        try { return JSON.parse(text); } catch (err) { return fallback; }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readLocalJson(key, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            var text = window.localStorage ? window.localStorage.getItem(key) : null;&lt;br /&gt;
            return text ? safeJsonParse(text, fallback) : fallback;&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return fallback;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function writeLocalJson(key, value) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.localStorage) window.localStorage.setItem(key, JSON.stringify(value));&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeManifestTitle(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var match;&lt;br /&gt;
        var i;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        for (i = 0; i &amp;lt; 3; i += 1) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
            } catch (err) { break; }&lt;br /&gt;
        }&lt;br /&gt;
        match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
        if (match) text = match[1];&lt;br /&gt;
        text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^\/+/, &#039;&#039;)&lt;br /&gt;
            .replace(/^index\.php\/?/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^wiki\/?/i, &#039;&#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
        match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/([^?#]+)(?:[?#].*)?$/i);&lt;br /&gt;
        if (match) text = &#039;File:&#039; + match[1];&lt;br /&gt;
        text = text.split(&#039;#&#039;)[0].replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
        text = text.replace(/^(?:파일|Image|이미지)\s*:/i, &#039;File:&#039;);&lt;br /&gt;
        if (/^(?:mediawiki|template|module|manage|file)\s*:/i.test(text)) {&lt;br /&gt;
            text = text.replace(/^([^:]+)\s*:\s*/, function (_, ns) { return ns.charAt(0).toUpperCase() + ns.slice(1).toLowerCase() + &#039;:&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
        if (/^File:/i.test(text)) text = &#039;File:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        if (/^Mediawiki:/i.test(text)) text = &#039;MediaWiki:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        return text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resourceToken(resource) {&lt;br /&gt;
        if (!resource || typeof resource !== &#039;object&#039;) return &#039;&#039;;&lt;br /&gt;
        return String(resource.revision || resource.sha1 || resource.hash || resource.timestamp || resource.updatedAt || resource.url || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createEntryCache() {&lt;br /&gt;
        var index = readLocalJson(ENTRY_CACHE_INDEX_KEY, { entries: {} }) || { entries: {} };&lt;br /&gt;
        var packs = readLocalJson(ENTRY_CACHE_PACK_KEY, { packs: {} }) || { packs: {} };&lt;br /&gt;
        var objectUrls = {};&lt;br /&gt;
        var stats = {&lt;br /&gt;
            textHits: 0,&lt;br /&gt;
            blobHits: 0,&lt;br /&gt;
            misses: 0,&lt;br /&gt;
            networkStores: 0,&lt;br /&gt;
            stores: 0,&lt;br /&gt;
            deletes: 0&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        function ensureIndex() {&lt;br /&gt;
            if (!index || typeof index !== &#039;object&#039;) index = { entries: {} };&lt;br /&gt;
            if (!index.entries || typeof index.entries !== &#039;object&#039;) index.entries = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactIndexForLocalStorage() {&lt;br /&gt;
            var entries;&lt;br /&gt;
            var next = {};&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var entry = entries[key] || {};&lt;br /&gt;
                var kind = String(entry.kind || &#039;raw&#039;).toLowerCase();&lt;br /&gt;
                /*&lt;br /&gt;
                 * CacheStorage already owns image/blob bodies.  Keeping one localStorage&lt;br /&gt;
                 * metadata row per flag/blob competes with MediaWiki ResourceLoader&#039;s own&lt;br /&gt;
                 * localStorage module store and can trigger QuotaExceededError before our&lt;br /&gt;
                 * code even runs on the next page load.  Text/json/file URL rows stay indexed&lt;br /&gt;
                 * because they are small and useful for invalidation diagnostics.&lt;br /&gt;
                 */&lt;br /&gt;
                if (kind === &#039;image&#039; || kind === &#039;blob&#039;) return;&lt;br /&gt;
                next[key] = entry;&lt;br /&gt;
            });&lt;br /&gt;
            index.entries = next;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensurePacks() {&lt;br /&gt;
            if (!packs || typeof packs !== &#039;object&#039;) packs = { packs: {} };&lt;br /&gt;
            if (!packs.packs || typeof packs.packs !== &#039;object&#039;) packs.packs = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function saveIndex() {&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            compactIndexForLocalStorage();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_INDEX_KEY, index);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function savePacks() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_PACK_KEY, packs);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function requestForKey(key) {&lt;br /&gt;
            return new Request(ENTRY_CACHE_REQUEST_PREFIX + encodeURIComponent(String(key || &#039;&#039;)), { credentials: &#039;same-origin&#039; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function openCache() {&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return window.caches.open(ENTRY_CACHE_NAME).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheEntry(key, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            index.entries[key] = Object.assign({}, index.entries[key] || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                resourceKey: String(meta &amp;amp;&amp;amp; meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                token: String(meta &amp;amp;&amp;amp; meta.token || &#039;&#039;),&lt;br /&gt;
                kind: String(meta &amp;amp;&amp;amp; meta.kind || &#039;raw&#039;),&lt;br /&gt;
                contentType: String(meta &amp;amp;&amp;amp; meta.contentType || &#039;&#039;),&lt;br /&gt;
                size: Number(meta &amp;amp;&amp;amp; meta.size || 0) || null,&lt;br /&gt;
                createdAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].createdAt ? index.entries[key].createdAt : now(),&lt;br /&gt;
                updatedAt: now(),&lt;br /&gt;
                lastHitAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].lastHitAt ? index.entries[key].lastHitAt : null,&lt;br /&gt;
                hits: Number(index.entries[key] &amp;amp;&amp;amp; index.entries[key].hits || 0)&lt;br /&gt;
            });&lt;br /&gt;
            saveIndex();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function markHit(key, field) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            if (index.entries[key]) {&lt;br /&gt;
                index.entries[key].hits = Number(index.entries[key].hits || 0) + 1;&lt;br /&gt;
                index.entries[key].lastHitAt = now();&lt;br /&gt;
                saveIndex();&lt;br /&gt;
            }&lt;br /&gt;
            if (field &amp;amp;&amp;amp; stats[field] != null) stats[field] += 1;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getResponse(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return null;&lt;br /&gt;
                return cache.match(requestForKey(key)).then(function (res) {&lt;br /&gt;
                    if (!res) {&lt;br /&gt;
                        stats.misses += 1;&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }&lt;br /&gt;
                    return res;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putResponse(key, response, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !response || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var cloned;&lt;br /&gt;
                var headers;&lt;br /&gt;
                var bodyPromise;&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                cloned = response.clone();&lt;br /&gt;
                bodyPromise = cloned.blob().catch(function () { return null; });&lt;br /&gt;
                return bodyPromise.then(function (blob) {&lt;br /&gt;
                    if (!blob) return false;&lt;br /&gt;
                    headers = new Headers(response.headers || {});&lt;br /&gt;
                    if (!headers.get(&#039;Content-Type&#039;)) headers.set(&#039;Content-Type&#039;, meta.contentType || blob.type || &#039;application/octet-stream&#039;);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cache-Key&#039;, key);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Resource-Key&#039;, String(meta.resourceKey || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Revision-Token&#039;, String(meta.token || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cached-At&#039;, String(now()));&lt;br /&gt;
                    return cache.put(requestForKey(key), new Response(blob, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                        stats.stores += 1;&lt;br /&gt;
                        cacheEntry(key, {&lt;br /&gt;
                            resourceKey: meta.resourceKey,&lt;br /&gt;
                            token: meta.token,&lt;br /&gt;
                            kind: meta.kind || &#039;blob&#039;,&lt;br /&gt;
                            contentType: headers.get(&#039;Content-Type&#039;) || blob.type || &#039;&#039;,&lt;br /&gt;
                            size: blob.size&lt;br /&gt;
                        });&lt;br /&gt;
                        return true;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getText(key) {&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return null;&lt;br /&gt;
                markHit(key, &#039;textHits&#039;);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putText(key, text, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var headers;&lt;br /&gt;
                var body = String(text || &#039;&#039;);&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                headers = new Headers({&lt;br /&gt;
                    &#039;Content-Type&#039;: meta.contentType || &#039;text/plain; charset=UTF-8&#039;,&lt;br /&gt;
                    &#039;X-Entry-Cache-Key&#039;: key,&lt;br /&gt;
                    &#039;X-Entry-Resource-Key&#039;: String(meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Revision-Token&#039;: String(meta.token || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Cached-At&#039;: String(now())&lt;br /&gt;
                });&lt;br /&gt;
                return cache.put(requestForKey(key), new Response(body, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                    stats.stores += 1;&lt;br /&gt;
                    cacheEntry(key, {&lt;br /&gt;
                        resourceKey: meta.resourceKey,&lt;br /&gt;
                        token: meta.token,&lt;br /&gt;
                        kind: meta.kind || &#039;text&#039;,&lt;br /&gt;
                        contentType: headers.get(&#039;Content-Type&#039;),&lt;br /&gt;
                        size: body.length&lt;br /&gt;
                    });&lt;br /&gt;
                    return true;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getBlobUrl(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                return Promise.resolve(objectUrls[key]);&lt;br /&gt;
            }&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return &#039;&#039;;&lt;br /&gt;
                return res.blob().then(function (blob) {&lt;br /&gt;
                    if (!blob || !blob.size) return &#039;&#039;;&lt;br /&gt;
                    objectUrls[key] = URL.createObjectURL(blob);&lt;br /&gt;
                    markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                    return objectUrls[key];&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchBlobUrl(url, key, meta, options) {&lt;br /&gt;
            url = String(url || &#039;&#039;).trim();&lt;br /&gt;
            key = String(key || &#039;&#039;).trim();&lt;br /&gt;
            if (!url || !key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            return getBlobUrl(key).then(function (cachedUrl) {&lt;br /&gt;
                if (cachedUrl) return cachedUrl;&lt;br /&gt;
                return fetch(url, {&lt;br /&gt;
                    credentials: &#039;same-origin&#039;,&lt;br /&gt;
                    cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
                }).then(function (res) {&lt;br /&gt;
                    if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                    stats.networkStores += 1;&lt;br /&gt;
                    return putResponse(key, res, Object.assign({}, meta || {}, { kind: meta &amp;amp;&amp;amp; meta.kind ? meta.kind : &#039;blob&#039; })).then(function () {&lt;br /&gt;
                        return getBlobUrl(key);&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return &#039;&#039;;&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function deleteKey(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
                delete objectUrls[key];&lt;br /&gt;
            }&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                return cache.delete(requestForKey(key)).then(function (ok) {&lt;br /&gt;
                    ensureIndex();&lt;br /&gt;
                    if (index.entries &amp;amp;&amp;amp; index.entries[key]) {&lt;br /&gt;
                        delete index.entries[key];&lt;br /&gt;
                        stats.deletes += 1;&lt;br /&gt;
                        saveIndex();&lt;br /&gt;
                    }&lt;br /&gt;
                    return ok;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function invalidateResources(resourceKeys) {&lt;br /&gt;
            var map = {};&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys = [];&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            (resourceKeys || []).forEach(function (resourceKey) {&lt;br /&gt;
                resourceKey = String(resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (resourceKey) map[resourceKey] = true;&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(entries).forEach(function (cacheKey) {&lt;br /&gt;
                var resourceKey = String(entries[cacheKey] &amp;amp;&amp;amp; entries[cacheKey].resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (map[resourceKey]) keys.push(cacheKey);&lt;br /&gt;
            });&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function prune(maxAgeDays) {&lt;br /&gt;
            var days = Math.max(1, Number(maxAgeDays) || 7);&lt;br /&gt;
            var cutoff = now() - days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys;&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            keys = Object.keys(entries).filter(function (key) {&lt;br /&gt;
                return Number(entries[key] &amp;amp;&amp;amp; entries[key].createdAt || 0) &amp;lt; cutoff;&lt;br /&gt;
            });&lt;br /&gt;
            window.localStorage &amp;amp;&amp;amp; window.localStorage.setItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY, String(now()));&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function reset() {&lt;br /&gt;
            Object.keys(objectUrls).forEach(function (key) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
            });&lt;br /&gt;
            objectUrls = {};&lt;br /&gt;
            index = { entries: {} };&lt;br /&gt;
            packs = { packs: {} };&lt;br /&gt;
            saveIndex();&lt;br /&gt;
            savePacks();&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            return window.caches.delete(ENTRY_CACHE_NAME).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packReady(key, token) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            return !!(packs.packs[key] &amp;amp;&amp;amp; String(packs.packs[key].token || &#039;&#039;) === token &amp;amp;&amp;amp; packs.packs[key].ready);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setPackReady(key, token, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            packs.packs[key] = Object.assign({}, meta || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                token: token,&lt;br /&gt;
                ready: true,&lt;br /&gt;
                updatedAt: now()&lt;br /&gt;
            });&lt;br /&gt;
            savePacks();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packInfo() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            return Object.assign({}, packs.packs || {});&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function info() {&lt;br /&gt;
            var lastClientPrune = 0;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var byKind = {};&lt;br /&gt;
            try { lastClientPrune = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var kind = String(entries[key] &amp;amp;&amp;amp; entries[key].kind || &#039;raw&#039;);&lt;br /&gt;
                byKind[kind] = (byKind[kind] || 0) + 1;&lt;br /&gt;
            });&lt;br /&gt;
            return {&lt;br /&gt;
                supported: hasCacheStorage(),&lt;br /&gt;
                name: ENTRY_CACHE_NAME,&lt;br /&gt;
                entries: Object.keys(entries).length,&lt;br /&gt;
                byKind: byKind,&lt;br /&gt;
                packs: Object.keys(packs.packs || {}).length,&lt;br /&gt;
                lastClientPruneAt: lastClientPrune || null,&lt;br /&gt;
                stats: Object.assign({}, stats)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Trim legacy image/blob metadata rows produced by older builds. */&lt;br /&gt;
        try { saveIndex(); } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            getResponse: getResponse,&lt;br /&gt;
            putResponse: putResponse,&lt;br /&gt;
            getText: getText,&lt;br /&gt;
            putText: putText,&lt;br /&gt;
            getBlobUrl: getBlobUrl,&lt;br /&gt;
            fetchBlobUrl: fetchBlobUrl,&lt;br /&gt;
            invalidateResources: invalidateResources,&lt;br /&gt;
            prune: prune,&lt;br /&gt;
            reset: reset,&lt;br /&gt;
            packReady: packReady,&lt;br /&gt;
            setPackReady: setPackReady,&lt;br /&gt;
            packInfo: packInfo,&lt;br /&gt;
            info: info&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryCache = window.EntryCache || createEntryCache();&lt;br /&gt;
&lt;br /&gt;
    function createRevisionManifestService() {&lt;br /&gt;
        var current = null;&lt;br /&gt;
        var previous = readLocalJson(REVISION_MANIFEST_LOCAL_KEY, null);&lt;br /&gt;
        var loadPromise = null;&lt;br /&gt;
        var changedResources = [];&lt;br /&gt;
&lt;br /&gt;
        function unwrap(payload) {&lt;br /&gt;
            return payload &amp;amp;&amp;amp; (payload.entryrevisionmanifest || payload.revisionManifest || payload) || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourcesOf(manifest) {&lt;br /&gt;
            return manifest &amp;amp;&amp;amp; manifest.resources &amp;amp;&amp;amp; typeof manifest.resources === &#039;object&#039; ? manifest.resources : {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactRevisionManifest(manifest) {&lt;br /&gt;
            var out;&lt;br /&gt;
            var resources;&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039;) return null;&lt;br /&gt;
            out = {&lt;br /&gt;
                manifestVersion: manifest.manifestVersion || manifest.version || &#039;&#039;,&lt;br /&gt;
                version: manifest.version || manifest.manifestVersion || &#039;&#039;,&lt;br /&gt;
                generatedAt: manifest.generatedAt || &#039;&#039;,&lt;br /&gt;
                pruneDays: manifest.pruneDays,&lt;br /&gt;
                clientPruneDays: manifest.clientPruneDays,&lt;br /&gt;
                resources: {}&lt;br /&gt;
            };&lt;br /&gt;
            resources = resourcesOf(manifest);&lt;br /&gt;
            Object.keys(resources).forEach(function (title) {&lt;br /&gt;
                var src = resources[title];&lt;br /&gt;
                var dst;&lt;br /&gt;
                if (!src || typeof src !== &#039;object&#039;) return;&lt;br /&gt;
                dst = {};&lt;br /&gt;
                [&#039;revision&#039;, &#039;sha1&#039;, &#039;hash&#039;, &#039;timestamp&#039;, &#039;updatedAt&#039;, &#039;url&#039;].forEach(function (key) {&lt;br /&gt;
                    if (src[key] !== undefined &amp;amp;&amp;amp; src[key] !== null &amp;amp;&amp;amp; String(src[key]) !== &#039;&#039;) dst[key] = src[key];&lt;br /&gt;
                });&lt;br /&gt;
                out.resources[title] = dst;&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function buildLookup(manifest) {&lt;br /&gt;
            var lookup = {};&lt;br /&gt;
            Object.keys(resourcesOf(manifest)).forEach(function (title) {&lt;br /&gt;
                lookup[normalizeManifestTitle(title).toLowerCase()] = resourcesOf(manifest)[title];&lt;br /&gt;
            });&lt;br /&gt;
            return lookup;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function computeChanged(prev, next) {&lt;br /&gt;
            var prevLookup = buildLookup(prev);&lt;br /&gt;
            var nextLookup = buildLookup(next);&lt;br /&gt;
            var out = [];&lt;br /&gt;
            Object.keys(nextLookup).forEach(function (key) {&lt;br /&gt;
                if (resourceToken(prevLookup[key]) !== resourceToken(nextLookup[key])) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(prevLookup).forEach(function (key) {&lt;br /&gt;
                if (!nextLookup[key]) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            return unique(out);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function load(options) {&lt;br /&gt;
            if (loadPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return loadPromise;&lt;br /&gt;
            loadPromise = fetchApi({&lt;br /&gt;
                action: REVISION_MANIFEST_ACTION,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;&lt;br /&gt;
            }).then(function (payload) {&lt;br /&gt;
                var manifest = unwrap(payload);&lt;br /&gt;
                if (!manifest || !manifest.resources) throw new Error(&#039;invalid revision manifest&#039;);&lt;br /&gt;
                current = compactRevisionManifest(manifest) || manifest;&lt;br /&gt;
                changedResources = computeChanged(previous, current);&lt;br /&gt;
                if (changedResources.length &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.invalidateResources === &#039;function&#039;) {&lt;br /&gt;
                    window.EntryCache.invalidateResources(changedResources);&lt;br /&gt;
                }&lt;br /&gt;
                writeLocalJson(REVISION_MANIFEST_LOCAL_KEY, current);&lt;br /&gt;
                previous = current;&lt;br /&gt;
                maybePrune();&lt;br /&gt;
                return current;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                current = previous || null;&lt;br /&gt;
                return current;&lt;br /&gt;
            });&lt;br /&gt;
            return loadPromise;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensureLoaded() {&lt;br /&gt;
            return current ? Promise.resolve(current) : load();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            var lookup;&lt;br /&gt;
            if (!title || !current) return null;&lt;br /&gt;
            lookup = buildLookup(current);&lt;br /&gt;
            return lookup[title.toLowerCase()] || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenForRef(ref) {&lt;br /&gt;
            return resourceToken(resourceForRef(ref));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceKeyForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            return title ? title.toLowerCase() : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForRef(ref, type) {&lt;br /&gt;
            var resourceKey = resourceKeyForRef(ref);&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            if (!resourceKey || !token) return &#039;&#039;;&lt;br /&gt;
            return String(type || &#039;raw&#039;) + &#039;:&#039; + resourceKey + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function manifestToken(extra) {&lt;br /&gt;
            var base = current &amp;amp;&amp;amp; (current.manifestVersion || current.version || current.generatedAt || &#039;&#039;) || &#039;&#039;;&lt;br /&gt;
            return String(base || &#039;no-manifest&#039;) + (extra ? (&#039;:&#039; + String(extra)) : &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenFromUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;);&lt;br /&gt;
            var match = text.match(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=([^&amp;amp;]+)/);&lt;br /&gt;
            if (!match) return &#039;&#039;;&lt;br /&gt;
            try { return decodeURIComponent(match[1]); } catch (err) { return match[1]; }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForUrl(url, type) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var token = tokenFromUrl(text) || manifestToken(&#039;url&#039;);&lt;br /&gt;
            var normalized;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                normalized = new URL(text, window.location.href);&lt;br /&gt;
                text = normalized.pathname + (normalized.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            text = text.replace(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=[^&amp;amp;]*/g, &#039;&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            try { text = decodeURI(text); } catch (err2) {}&lt;br /&gt;
            return String(type || &#039;blob&#039;) + &#039;:url:&#039; + text.toLowerCase() + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function addRevisionParam(url, ref) {&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var sep;&lt;br /&gt;
            if (!text || !token || /[?&amp;amp;]_entryRev=/.test(text)) return text;&lt;br /&gt;
            sep = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + sep + &#039;_entryRev=&#039; + encodeURIComponent(token);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function maybePrune() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var due;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            due = !last || (now() - last &amp;gt;= days * 24 * 60 * 60 * 1000);&lt;br /&gt;
            if (due &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.prune === &#039;function&#039;) {&lt;br /&gt;
                window.EntryCache.prune(days);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function countdown() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var next;&lt;br /&gt;
            var remain;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            if (!last) return { days: days, lastClientPruneAt: null, nextClientPruneAt: null, remainingMs: 0, remainingDays: 0, due: true };&lt;br /&gt;
            next = last + days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            remain = Math.max(0, next - now());&lt;br /&gt;
            return {&lt;br /&gt;
                days: days,&lt;br /&gt;
                lastClientPruneAt: last,&lt;br /&gt;
                nextClientPruneAt: next,&lt;br /&gt;
                remainingMs: remain,&lt;br /&gt;
                remainingDays: Math.ceil(remain / (24 * 60 * 60 * 1000)),&lt;br /&gt;
                due: remain &amp;lt;= 0&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function status() {&lt;br /&gt;
            return {&lt;br /&gt;
                available: !!current,&lt;br /&gt;
                manifestVersion: current &amp;amp;&amp;amp; (current.manifestVersion || current.version || &#039;&#039;),&lt;br /&gt;
                generatedAt: current &amp;amp;&amp;amp; current.generatedAt || &#039;&#039;,&lt;br /&gt;
                changedResources: changedResources.slice(0, 50),&lt;br /&gt;
                changedCount: changedResources.length,&lt;br /&gt;
                prune: countdown(),&lt;br /&gt;
                cache: window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.info === &#039;function&#039; ? window.EntryCache.info() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            load: load,&lt;br /&gt;
            ensureLoaded: ensureLoaded,&lt;br /&gt;
            current: function () { return current; },&lt;br /&gt;
            resourceForRef: resourceForRef,&lt;br /&gt;
            tokenForRef: tokenForRef,&lt;br /&gt;
            cacheKeyForRef: cacheKeyForRef,&lt;br /&gt;
            cacheKeyForUrl: cacheKeyForUrl,&lt;br /&gt;
            manifestToken: manifestToken,&lt;br /&gt;
            resourceKeyForRef: resourceKeyForRef,&lt;br /&gt;
            addRevisionParam: addRevisionParam,&lt;br /&gt;
            maybePrune: maybePrune,&lt;br /&gt;
            countdown: countdown,&lt;br /&gt;
            status: status&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.RevisionManifest = window.RevisionManifest || createRevisionManifestService();&lt;br /&gt;
&lt;br /&gt;
    function createEntryStore() {&lt;br /&gt;
        var jsonCache = {};&lt;br /&gt;
        var flagUrlCache = {};&lt;br /&gt;
        var fileUrlCache = {};&lt;br /&gt;
        var imageReadyCache = {};&lt;br /&gt;
        var imageObjectCache = {};&lt;br /&gt;
        var imageDisplayUrlCache = {};&lt;br /&gt;
        var imagePromiseCache = {};&lt;br /&gt;
        var rawPromiseCache = {};&lt;br /&gt;
        var textCache = {};&lt;br /&gt;
        var textPromiseCache = {};&lt;br /&gt;
&lt;br /&gt;
        function fetchJsonRef(ref, options) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var promiseKey;&lt;br /&gt;
            var url;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty json ref&#039;));&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(ref, &#039;json&#039;) : &#039;&#039;;&lt;br /&gt;
            promiseKey = cacheKey ? (key + &#039;@&#039; + cacheKey) : key;&lt;br /&gt;
            if (jsonCache[promiseKey]) return Promise.resolve(jsonCache[promiseKey].data);&lt;br /&gt;
            if (rawPromiseCache[promiseKey]) return rawPromiseCache[promiseKey];&lt;br /&gt;
            url = rawUrlForRef(ref, &#039;application/json&#039;);&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
            }&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(ref) : key;&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(ref) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                rawPromiseCache[promiseKey] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        var cachedData = cachedText &amp;amp;&amp;amp; cachedText.trim() ? JSON.parse(cachedText) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: cachedData, loadedAt: now(), persistent: true };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        return cachedData;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token });&lt;br /&gt;
                        return data;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return rawPromiseCache[promiseKey];&lt;br /&gt;
            }&lt;br /&gt;
            rawPromiseCache[promiseKey] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                return data;&lt;br /&gt;
            });&lt;br /&gt;
            return rawPromiseCache[promiseKey];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getJsonSync(ref) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            return key &amp;amp;&amp;amp; jsonCache[key] ? jsonCache[key].data : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setJsonRef(ref, data) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            jsonCache[key] = { key: key, ref: ref, url: rawUrlForRef(ref, &#039;application/json&#039;), data: data, loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeFile(value) {&lt;br /&gt;
            return String(value || &#039;&#039;)&lt;br /&gt;
                .replace(/^(?:file|파일):/i, &#039;&#039;)&lt;br /&gt;
                .trim();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileKey(value) {&lt;br /&gt;
            return normalizeFile(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setFlagUrl(file, url) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            flagUrlCache[key] = String(url || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getFlagUrl(file) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            return key ? (flagUrlCache[key] || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFlagUrls(files) {&lt;br /&gt;
            var clean = unique((files || []).map(normalizeFile).filter(Boolean));&lt;br /&gt;
&lt;br /&gt;
            function cacheKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;flagurl&#039;) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function resourceKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + fileKey(file));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function tokenForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function hydrateCachedFlagUrl(file) {&lt;br /&gt;
                var key = fileKey(file);&lt;br /&gt;
                var cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                if (!key || flagUrlCache[key] !== undefined || !cacheKey || !window.EntryCache || typeof window.EntryCache.getText !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.resolve(false);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        flagUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return true;&lt;br /&gt;
                    }&lt;br /&gt;
                    return false;&lt;br /&gt;
                }).catch(function () { return false; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(clean.map(hydrateCachedFlagUrl)).then(function () {&lt;br /&gt;
                var pending = clean.filter(function (file) {&lt;br /&gt;
                    return getFlagUrl(file) === &#039;&#039; &amp;amp;&amp;amp; flagUrlCache[fileKey(file)] === undefined;&lt;br /&gt;
                });&lt;br /&gt;
                var chunks = [];&lt;br /&gt;
&lt;br /&gt;
                while (pending.length) chunks.push(pending.splice(0, 20));&lt;br /&gt;
                if (!chunks.length) return flagUrlCache;&lt;br /&gt;
&lt;br /&gt;
                return Promise.all(chunks.map(function (chunk) {&lt;br /&gt;
                    var titleToKey = {};&lt;br /&gt;
                    var keyToFile = {};&lt;br /&gt;
                    var titles = [];&lt;br /&gt;
                    chunk.forEach(function (file) {&lt;br /&gt;
                        var key = fileKey(file);&lt;br /&gt;
                        if (!key) return;&lt;br /&gt;
                        titleToKey[fileKey(&#039;File:&#039; + file)] = key;&lt;br /&gt;
                        titleToKey[fileKey(&#039;파일:&#039; + file)] = key;&lt;br /&gt;
                        keyToFile[key] = file;&lt;br /&gt;
                        titles.push(&#039;File:&#039; + file);&lt;br /&gt;
                        titles.push(&#039;파일:&#039; + file);&lt;br /&gt;
                    });&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size&#039;,&lt;br /&gt;
                        iiurlwidth: &#039;16&#039;,&lt;br /&gt;
                        titles: titles.join(&#039;|&#039;)&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var seen = {};&lt;br /&gt;
                        pages.forEach(function (page) {&lt;br /&gt;
                            var key = titleToKey[fileKey(page &amp;amp;&amp;amp; page.title)] || fileKey(page &amp;amp;&amp;amp; page.title);&lt;br /&gt;
                            var file = keyToFile[key] || (page &amp;amp;&amp;amp; page.title || &#039;&#039;).replace(/^(?:File|파일):/i, &#039;&#039;);&lt;br /&gt;
                            var imageinfo = page &amp;amp;&amp;amp; page.imageinfo &amp;amp;&amp;amp; page.imageinfo[0];&lt;br /&gt;
                            var url;&lt;br /&gt;
                            var rev;&lt;br /&gt;
                            var sep;&lt;br /&gt;
                            var cacheKey;&lt;br /&gt;
                            if (!key) return;&lt;br /&gt;
                            seen[key] = true;&lt;br /&gt;
                            url = imageinfo &amp;amp;&amp;amp; (imageinfo.thumburl || imageinfo.url) ? (imageinfo.thumburl || imageinfo.url) : &#039;&#039;;&lt;br /&gt;
                            if (url) {&lt;br /&gt;
                                rev = imageinfo &amp;amp;&amp;amp; (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) ? (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) : &#039;&#039;;&lt;br /&gt;
                                if (rev &amp;amp;&amp;amp; !/[?&amp;amp;]_entryFileRev=/.test(url)) {&lt;br /&gt;
                                    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                                    url += sep + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                                }&lt;br /&gt;
                            }&lt;br /&gt;
                            flagUrlCache[key] = url;&lt;br /&gt;
                            cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.putText === &#039;function&#039;) {&lt;br /&gt;
                                window.EntryCache.putText(cacheKey, url || &#039;&#039;, {&lt;br /&gt;
                                    resourceKey: resourceKeyForFile(file),&lt;br /&gt;
                                    token: tokenForFile(file),&lt;br /&gt;
                                    kind: &#039;flagurl&#039;,&lt;br /&gt;
                                    contentType: &#039;text/plain; charset=UTF-8&#039;&lt;br /&gt;
                                });&lt;br /&gt;
                            }&lt;br /&gt;
                        });&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; !seen[key] &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () { return flagUrlCache; });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeGenericFileTitle(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            var match;&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            for (i = 0; i &amp;lt; 4; i += 1) {&lt;br /&gt;
                try {&lt;br /&gt;
                    if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;).replace(/^\/+/, &#039;&#039;).replace(/^index\.php\/?/i, &#039;&#039;).replace(/^wiki\/?/i, &#039;&#039;).trim();&lt;br /&gt;
            match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/(.+)$/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^(?:File|파일|Image|이미지)\s*:/i, &#039;&#039;).replace(/^:+/, &#039;&#039;).trim();&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isFileRef(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            return /^(?:file|파일)\s*:/i.test(text) || /(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\//i.test(text);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileUrlKey(value) {&lt;br /&gt;
            return normalizeGenericFileTitle(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function stableDirectImageUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var separator;&lt;br /&gt;
            var bust;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            if (/[?&amp;amp;]_entryAsset=/.test(text) || /[?&amp;amp;]_=/.test(text)) return text;&lt;br /&gt;
            bust = String(BUILD_ID || &#039;entry&#039;);&lt;br /&gt;
            separator = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + separator + &#039;_entryAsset=&#039; + encodeURIComponent(bust);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFileUrl(ref) {&lt;br /&gt;
            var original = String(ref || &#039;&#039;).trim();&lt;br /&gt;
            var file;&lt;br /&gt;
            var key;&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!original) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (!isFileRef(original)) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            file = normalizeGenericFileTitle(original);&lt;br /&gt;
            key = fileUrlKey(file);&lt;br /&gt;
            if (!file || !key) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            if (fileUrlCache[key]) return Promise.resolve(fileUrlCache[key]);&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;fileurl&#039;) : &#039;&#039;;&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + key);&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        fileUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return fileUrlCache[key];&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                        titles: &#039;File:&#039; + file&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                        var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                        var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                        var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                        var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                        fileUrlCache[key] = resolved;&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, resolved, { resourceKey: resourceKey, token: token, kind: &#039;fileurl&#039;, contentType: &#039;text/plain; charset=UTF-8&#039; });&lt;br /&gt;
                        return resolved;&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return stableDirectImageUrl(original);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
            return fetchApi({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;,&lt;br /&gt;
                redirects: &#039;1&#039;,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + file&lt;br /&gt;
            }).then(function (json) {&lt;br /&gt;
                var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                fileUrlCache[key] = resolved;&lt;br /&gt;
                return resolved;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                return stableDirectImageUrl(original);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImageUrl(url, options) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var sourcePromise;&lt;br /&gt;
            var persistent = !(options &amp;amp;&amp;amp; options.persistent === false);&lt;br /&gt;
            if (!key) return Promise.resolve(false);&lt;br /&gt;
            if (imageReadyCache[key]) return Promise.resolve(true);&lt;br /&gt;
            if (imagePromiseCache[key]) return imagePromiseCache[key];&lt;br /&gt;
&lt;br /&gt;
            cacheKey = persistent &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039; ? window.RevisionManifest.cacheKeyForUrl(key, &#039;image&#039;) : &#039;&#039;;&lt;br /&gt;
            sourcePromise = Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.fetchBlobUrl === &#039;function&#039;) {&lt;br /&gt;
                sourcePromise = window.EntryCache.fetchBlobUrl(key, cacheKey, {&lt;br /&gt;
                    resourceKey: &#039;&#039;,&lt;br /&gt;
                    token: &#039;&#039;,&lt;br /&gt;
                    kind: &#039;image&#039;&lt;br /&gt;
                }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            imagePromiseCache[key] = sourcePromise.then(function (cachedObjectUrl) {&lt;br /&gt;
                return new Promise(function (resolve) {&lt;br /&gt;
                    var img = new Image();&lt;br /&gt;
                    var settled = false;&lt;br /&gt;
                    var src = cachedObjectUrl || key;&lt;br /&gt;
                    function finish(ok) {&lt;br /&gt;
                        if (settled) return;&lt;br /&gt;
                        settled = true;&lt;br /&gt;
                        if (ok) {&lt;br /&gt;
                            imageReadyCache[key] = true;&lt;br /&gt;
                            imageReadyCache[src] = true;&lt;br /&gt;
                            imageObjectCache[key] = img;&lt;br /&gt;
                            imageObjectCache[src] = img;&lt;br /&gt;
                            imageDisplayUrlCache[key] = src;&lt;br /&gt;
                            imageDisplayUrlCache[src] = src;&lt;br /&gt;
                        }&lt;br /&gt;
                        resolve(!!ok);&lt;br /&gt;
                    }&lt;br /&gt;
                    img.onload = function () {&lt;br /&gt;
                        if (img.decode) {&lt;br /&gt;
                            img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        } else {&lt;br /&gt;
                            finish(true);&lt;br /&gt;
                        }&lt;br /&gt;
                    };&lt;br /&gt;
                    img.onerror = function () {&lt;br /&gt;
                        if (src !== key) {&lt;br /&gt;
                            src = key;&lt;br /&gt;
                            img.src = key;&lt;br /&gt;
                            return;&lt;br /&gt;
                        }&lt;br /&gt;
                        finish(false);&lt;br /&gt;
                    };&lt;br /&gt;
                    img.decoding = &#039;async&#039;;&lt;br /&gt;
                    img.loading = &#039;eager&#039;;&lt;br /&gt;
                    img.src = src;&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        if (img.decode) img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        else finish(true);&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
            return imagePromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImages(urls, options) {&lt;br /&gt;
            var list = unique((urls || []).filter(Boolean));&lt;br /&gt;
            var limit = options &amp;amp;&amp;amp; Number(options.limit);&lt;br /&gt;
            var concurrency = Math.max(1, Math.min(48, Number(options &amp;amp;&amp;amp; options.concurrency) || 24));&lt;br /&gt;
            var index = 0;&lt;br /&gt;
            var ok = 0;&lt;br /&gt;
            var fail = 0;&lt;br /&gt;
            if (Number.isFinite(limit) &amp;amp;&amp;amp; limit &amp;gt; 0) list = list.slice(0, limit);&lt;br /&gt;
            if (!list.length) return Promise.resolve({ total: 0, ok: 0, fail: 0 });&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                function pump() {&lt;br /&gt;
                    while (index &amp;lt; list.length &amp;amp;&amp;amp; concurrency &amp;gt; 0) {&lt;br /&gt;
                        (function (url) {&lt;br /&gt;
                            concurrency -= 1;&lt;br /&gt;
                            preloadImageUrl(url, options || {}).then(function (result) {&lt;br /&gt;
                                if (result) ok += 1;&lt;br /&gt;
                                else fail += 1;&lt;br /&gt;
                            }).catch(function () {&lt;br /&gt;
                                fail += 1;&lt;br /&gt;
                            }).then(function () {&lt;br /&gt;
                                concurrency += 1;&lt;br /&gt;
                                if (index &amp;gt;= list.length &amp;amp;&amp;amp; ok + fail &amp;gt;= list.length) resolve({ total: list.length, ok: ok, fail: fail });&lt;br /&gt;
                                else pump();&lt;br /&gt;
                            });&lt;br /&gt;
                        })(list[index++]);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                pump();&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isImageReady(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return !!(key &amp;amp;&amp;amp; imageReadyCache[key]);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageElement(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageObjectCache[key] || null) : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageDisplayUrl(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageDisplayUrlCache[key] || key) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeUrlKey(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var a;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                a = document.createElement(&#039;a&#039;);&lt;br /&gt;
                a.href = text;&lt;br /&gt;
                text = a.pathname + (a.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            try {&lt;br /&gt;
                text = decodeURI(text);&lt;br /&gt;
            } catch (err2) {}&lt;br /&gt;
            text = text.replace(/([?&amp;amp;])_=[^&amp;amp;]*/g, &#039;$1&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            text = text.replace(/_/g, &#039;_&#039;);&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchTextUrl(url, options) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceRef;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty text url&#039;));&lt;br /&gt;
            if (textCache[key]) return Promise.resolve(textCache[key].text);&lt;br /&gt;
            if (textPromiseCache[key]) return textPromiseCache[key];&lt;br /&gt;
            resourceRef = options &amp;amp;&amp;amp; options.resourceRef ? options.resourceRef : (options &amp;amp;&amp;amp; options.ref ? options.ref : &#039;&#039;);&lt;br /&gt;
            cacheKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(resourceRef, &#039;text&#039;) : &#039;&#039;;&lt;br /&gt;
            if (!cacheKey &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039;) cacheKey = window.RevisionManifest.cacheKeyForUrl(url, &#039;text&#039;);&lt;br /&gt;
            resourceKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(resourceRef) : (&#039;url:&#039; + key);&lt;br /&gt;
            token = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(resourceRef) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                textPromiseCache[key] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: cachedText, loadedAt: now(), persistent: true };&lt;br /&gt;
                        return cachedText;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token, kind: &#039;text&#039;, contentType: &#039;text/html; charset=UTF-8&#039; });&lt;br /&gt;
                        return text;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return textPromiseCache[key];&lt;br /&gt;
            }&lt;br /&gt;
            textPromiseCache[key] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                return text;&lt;br /&gt;
            });&lt;br /&gt;
            return textPromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getTextSync(url) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            return key &amp;amp;&amp;amp; textCache[key] ? textCache[key].text : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setTextUrl(url, text) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            textCache[key] = { key: key, url: url, text: String(text || &#039;&#039;), loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheInfo() {&lt;br /&gt;
            return {&lt;br /&gt;
                json: Object.keys(jsonCache).length,&lt;br /&gt;
                jsonKeys: Object.keys(jsonCache),&lt;br /&gt;
                flags: Object.keys(flagUrlCache).length,&lt;br /&gt;
                files: Object.keys(fileUrlCache).length,&lt;br /&gt;
                images: Object.keys(imageReadyCache).length,&lt;br /&gt;
                retainedImages: Object.keys(imageObjectCache).length,&lt;br /&gt;
                displayImages: Object.keys(imageDisplayUrlCache).length,&lt;br /&gt;
                text: Object.keys(textCache).length,&lt;br /&gt;
                textKeys: Object.keys(textCache)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            fetchJsonRef: fetchJsonRef,&lt;br /&gt;
            getJsonSync: getJsonSync,&lt;br /&gt;
            setJsonRef: setJsonRef,&lt;br /&gt;
            normalizeRefKey: normalizeRefKey,&lt;br /&gt;
            rawUrlForRef: rawUrlForRef,&lt;br /&gt;
            revisionUrlForRef: function (ref, ctype) {&lt;br /&gt;
                var url = rawUrlForRef(ref, ctype);&lt;br /&gt;
                if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                    url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
                }&lt;br /&gt;
                return url;&lt;br /&gt;
            },&lt;br /&gt;
            resolveFlagUrls: resolveFlagUrls,&lt;br /&gt;
            setFlagUrl: setFlagUrl,&lt;br /&gt;
            getFlagUrl: getFlagUrl,&lt;br /&gt;
            resolveFileUrl: resolveFileUrl,&lt;br /&gt;
            preloadImageUrl: preloadImageUrl,&lt;br /&gt;
            preloadImages: preloadImages,&lt;br /&gt;
            isImageReady: isImageReady,&lt;br /&gt;
            getImageElement: getImageElement,&lt;br /&gt;
            getImageDisplayUrl: getImageDisplayUrl,&lt;br /&gt;
            fetchTextUrl: fetchTextUrl,&lt;br /&gt;
            getTextSync: getTextSync,&lt;br /&gt;
            setTextUrl: setTextUrl,&lt;br /&gt;
            stableDirectImageUrl: stableDirectImageUrl,&lt;br /&gt;
            cacheInfo: cacheInfo&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryStore = window.EntryStore || createEntryStore();&lt;br /&gt;
&lt;br /&gt;
    function updateBootProgress(done, total, label) {&lt;br /&gt;
        var pct = total ? Math.max(0, Math.min(100, Math.round((done / total) * 100))) : 0;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootDetailNode &amp;amp;&amp;amp; label) bootDetailNode.textContent = label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function adoptBootScreen(node) {&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        bootNode = node;&lt;br /&gt;
        bootStatusNode = bootNode.querySelector(&#039;.boot-gate-status&#039;);&lt;br /&gt;
        bootProgressNode = bootNode.querySelector(&#039;.boot-gate-progress&#039;);&lt;br /&gt;
        bootDetailNode = bootNode.querySelector(&#039;.boot-gate-detail&#039;);&lt;br /&gt;
        bootFillNode = bootNode.querySelector(&#039;.boot-gate-meter-fill&#039;);&lt;br /&gt;
        bootNode.classList.add(&#039;is-active&#039;);&lt;br /&gt;
        bootNode.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBootScreen(options) {&lt;br /&gt;
        var panel;&lt;br /&gt;
        var header;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var existing;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
        var previewMode;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        previewMode = !!options.preview;&lt;br /&gt;
        if (bootNode &amp;amp;&amp;amp; bootNode.parentNode) return bootNode;&lt;br /&gt;
        if (!document.body) return null;&lt;br /&gt;
&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !previewMode) return null;&lt;br /&gt;
        if (!previewMode) activateBootSurface();&lt;br /&gt;
&lt;br /&gt;
        existing = document.getElementById(&#039;boot-gate-screen&#039;) || (!previewMode &amp;amp;&amp;amp; window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.ensure ? window.__BootGatePrelude.ensure() : null);&lt;br /&gt;
        if (existing) return adoptBootScreen(existing);&lt;br /&gt;
&lt;br /&gt;
        bootNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootNode.id = &#039;boot-gate-screen&#039;;&lt;br /&gt;
        bootNode.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        bootNode.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        bootNode.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        header = document.createElement(&#039;div&#039;);&lt;br /&gt;
        header.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        header.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootStatusNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootStatusNode.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        bootStatusNode.textContent = &#039;Preparing entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        bootFillNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootFillNode.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(bootFillNode);&lt;br /&gt;
&lt;br /&gt;
        bootProgressNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootProgressNode.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        bootProgressNode.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootDetailNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootDetailNode.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        bootDetailNode.textContent = &#039;loading manifest&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { hideBootScreen({ force: true }); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(header);&lt;br /&gt;
        panel.appendChild(bootStatusNode);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(bootProgressNode);&lt;br /&gt;
        panel.appendChild(bootDetailNode);&lt;br /&gt;
        bootNode.appendChild(decoLayer);&lt;br /&gt;
        bootNode.appendChild(panel);&lt;br /&gt;
        bootNode.appendChild(close);&lt;br /&gt;
        document.body.appendChild(bootNode);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildLoginUrl() {&lt;br /&gt;
        var pageName = String(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        var params = {};&lt;br /&gt;
&lt;br /&gt;
        if (pageName &amp;amp;&amp;amp; !isAuthenticationPage() &amp;amp;&amp;amp; !isCreateAccountPage()) params.returnto = pageName;&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                return mw.util.getUrl(&#039;Special:UserLogin&#039;, params);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return &#039;/index.php?title=Special%3AUserLogin&#039; + (params.returnto ? &#039;&amp;amp;returnto=&#039; + encodeURIComponent(params.returnto) : &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureLoginGateAction(panel) {&lt;br /&gt;
        var copy;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (loginGateActionNode &amp;amp;&amp;amp; loginGateActionNode.parentNode) return loginGateActionNode;&lt;br /&gt;
        loginGateActionNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        loginGateActionNode.className = &#039;boot-gate-login&#039;;&lt;br /&gt;
&lt;br /&gt;
        copy = document.createElement(&#039;div&#039;);&lt;br /&gt;
        copy.className = &#039;boot-gate-login-copy&#039;;&lt;br /&gt;
        copy.textContent = &#039;SIGN IN TO ENTER THE WIKI.&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = document.createElement(&#039;a&#039;);&lt;br /&gt;
        action.className = &#039;boot-gate-login-action&#039;;&lt;br /&gt;
        action.href = buildLoginUrl();&lt;br /&gt;
        action.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        action.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
&lt;br /&gt;
        loginGateActionNode.appendChild(copy);&lt;br /&gt;
        loginGateActionNode.appendChild(action);&lt;br /&gt;
        panel.appendChild(loginGateActionNode);&lt;br /&gt;
        return loginGateActionNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showLoginGate() {&lt;br /&gt;
        var node = ensureBootScreen();&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (!node || !requiresLoginGate()) return false;&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        loginGateLocked = true;&lt;br /&gt;
        node.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.add(&#039;is-active&#039;, &#039;is-login-required&#039;);&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;dialog&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-modal&#039;, &#039;true&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;off&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = node.querySelector(&#039;.boot-gate-panel&#039;);&lt;br /&gt;
        title = node.querySelector(&#039;.boot-gate-title&#039;);&lt;br /&gt;
        if (title) title.textContent = &#039;ACCOUNT AUTHENTICATION&#039;;&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = &#039;LOGIN REQUIRED&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = &#039;100%&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = panel ? ensureLoginGateAction(panel).querySelector(&#039;.boot-gate-login-action&#039;) : null;&lt;br /&gt;
        if (action) {&lt;br /&gt;
            action.href = buildLoginUrl();&lt;br /&gt;
            window.requestAnimationFrame(function () {&lt;br /&gt;
                try { action.focus({ preventScroll: true }); }&lt;br /&gt;
                catch (err) { try { action.focus(); } catch (ignore) {} }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hideBootScreen(options) {&lt;br /&gt;
        var node = bootNode || document.getElementById(&#039;boot-gate-screen&#039;);&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        if (loginGateLocked &amp;amp;&amp;amp; requiresLoginGate() &amp;amp;&amp;amp; !options.force) return false;&lt;br /&gt;
        if (!node) {&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            if (bootNode === node) bootNode = null;&lt;br /&gt;
            loginGateActionNode = null;&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 240);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromNationPayload(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            if (typeof value === &#039;string&#039;) {&lt;br /&gt;
                files.push(value);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof value === &#039;object&#039;) files.push(value.file || value.flag_file || value.flag || value.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function scanItem(item) {&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            if (Array.isArray(item.flags)) item.flags.forEach(add);&lt;br /&gt;
            add(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        (payload &amp;amp;&amp;amp; payload.continents || []).forEach(function (continent) {&lt;br /&gt;
            (continent.regions || []).forEach(function (region) {&lt;br /&gt;
                (region.items || []).forEach(scanItem);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromLinkMap(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        var source = payload &amp;amp;&amp;amp; payload.items ? payload.items : {};&lt;br /&gt;
        Object.keys(source || {}).forEach(function (key) {&lt;br /&gt;
            var item = source[key];&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            files.push(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prewarmImages(urls, limit) {&lt;br /&gt;
        return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
            limit: Number(limit) &amp;gt; 0 ? Number(limit) : 0,&lt;br /&gt;
            concurrency: 16,&lt;br /&gt;
            persistent: false&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationRuntime() {&lt;br /&gt;
        return window.Decorations || window.CLBI_DECORATIONS || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationRuntime() {&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var tries = 0;&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var runtime = getDecorationRuntime();&lt;br /&gt;
                if (runtime) return resolve(runtime);&lt;br /&gt;
                tries += 1;&lt;br /&gt;
                if (tries &amp;gt; 40) return resolve(null);&lt;br /&gt;
                window.setTimeout(tick, 25);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesDecorationEntry(entry, filter) {&lt;br /&gt;
        var era = String(filter &amp;amp;&amp;amp; filter.era || &#039;&#039;).trim();&lt;br /&gt;
        var page = String(filter &amp;amp;&amp;amp; filter.page || &#039;&#039;).trim();&lt;br /&gt;
        var entryPage = String(entry &amp;amp;&amp;amp; entry.page || &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039;) return false;&lt;br /&gt;
        if (page &amp;amp;&amp;amp; entryPage &amp;amp;&amp;amp; entryPage !== page) return false;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() !== era) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDecorationSet(task, level) {&lt;br /&gt;
        var ref = task.ref || &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
        return window.EntryStore.fetchJsonRef(ref, { noStore: !!task.noStore }).then(function (registry) {&lt;br /&gt;
            var list = registry &amp;amp;&amp;amp; Array.isArray(registry.decorations) ? registry.decorations : [];&lt;br /&gt;
            var pixelRefs = [];&lt;br /&gt;
            list.forEach(function (entry) {&lt;br /&gt;
                var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).toLowerCase();&lt;br /&gt;
                var asset = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
                if (!asset) return;&lt;br /&gt;
                if (type !== &#039;pixel-json&#039; &amp;amp;&amp;amp; !/\.json(?:[?#].*)?$/i.test(asset)) return;&lt;br /&gt;
                if (!matchesDecorationEntry(entry, task)) return;&lt;br /&gt;
                pixelRefs.push(asset);&lt;br /&gt;
            });&lt;br /&gt;
            if (level !== &#039;full&#039; || task.preparePixels === false || !pixelRefs.length) return registry;&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (!runtime || typeof runtime.preparePixelCanvas !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                        return window.EntryStore.fetchJsonRef(pixelRef).catch(function () { return null; });&lt;br /&gt;
                    })).then(function () { return registry; });&lt;br /&gt;
                }&lt;br /&gt;
                return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(pixelRef).catch(function () { return null; });&lt;br /&gt;
                })).then(function () { return registry; });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareNationsEra(task, level) {&lt;br /&gt;
        var era = String(task.era || &#039;1950&#039;);&lt;br /&gt;
        var listRef = task.listRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_List.json&#039;);&lt;br /&gt;
        var linkRef = task.linkMapRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_Link_Map.json&#039;);&lt;br /&gt;
        var jsonPhaseId = BootPerf.start(&#039;nations-era json fetch&#039;, { era: era, level: level, listRef: listRef, linkRef: linkRef });&lt;br /&gt;
        var listPromise = window.EntryStore.fetchJsonRef(listRef).catch(function () { return null; });&lt;br /&gt;
        var linkPromise = window.EntryStore.fetchJsonRef(linkRef).catch(function () { return null; });&lt;br /&gt;
&lt;br /&gt;
        return Promise.all([listPromise, linkPromise]).then(function (results) {&lt;br /&gt;
            BootPerf.end(jsonPhaseId, { ok: true });&lt;br /&gt;
            var files;&lt;br /&gt;
            var flagUrls;&lt;br /&gt;
            if (level !== &#039;full&#039; &amp;amp;&amp;amp; level !== &#039;warm&#039;) return results;&lt;br /&gt;
            files = unique(collectFlagsFromNationPayload(results[0]).concat(collectFlagsFromLinkMap(results[1])));&lt;br /&gt;
            return BootPerf.measure(&#039;nations-era flag url resolve&#039;, { era: era, count: files.length }, function () {&lt;br /&gt;
                return window.EntryStore.resolveFlagUrls(files);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                if (level === &#039;warm&#039;) {&lt;br /&gt;
                    /*&lt;br /&gt;
                     * Warm tab contract — 20260708.&lt;br /&gt;
                     *&lt;br /&gt;
                     * A previous tab already stored the same-revision flag URL and image&lt;br /&gt;
                     * blobs.  Re-decoding every flag here makes the second tab feel as slow&lt;br /&gt;
                     * as the first one.  In warm mode we only hydrate the flag URL table from&lt;br /&gt;
                     * EntryCache so NationsPanel can render stable URLs immediately; image&lt;br /&gt;
                     * decode remains demand-driven by the live panel instead of blocking the&lt;br /&gt;
                     * boot screen.&lt;br /&gt;
                     */&lt;br /&gt;
                    return results;&lt;br /&gt;
                }&lt;br /&gt;
                flagUrls = files.map(function (file) { return window.EntryStore.getFlagUrl(file); }).filter(Boolean);&lt;br /&gt;
                /*&lt;br /&gt;
                 * 20260710: Flag URLs are part of the entry contract, but decoding every&lt;br /&gt;
                 * flag image is not.  The 1950 list has more than 150 flags, and waiting&lt;br /&gt;
                 * for all image decodes kept the public boot gate open for several seconds.&lt;br /&gt;
                 * Keep revision-aware URLs ready for the panel, then let the browser load&lt;br /&gt;
                 * the actual images from the live DOM.  A low-priority background warm-up&lt;br /&gt;
                 * may run after the boot gate has opened, so it never competes with the&lt;br /&gt;
                 * current entry surface or the globe ready contract.&lt;br /&gt;
                 */&lt;br /&gt;
                BootPerf.mark(&#039;nations-era flag image prewarm skipped&#039;, {&lt;br /&gt;
                    era: era,&lt;br /&gt;
                    count: flagUrls.length,&lt;br /&gt;
                    limit: task.flagLimit == null ? 0 : Number(task.flagLimit),&lt;br /&gt;
                    reason: &#039;deferred-after-boot-gate&#039;&lt;br /&gt;
                });&lt;br /&gt;
                if (flagUrls.length &amp;amp;&amp;amp; task.deferFlagImages !== false) {&lt;br /&gt;
                    deferredEntryWarmups.push(function () {&lt;br /&gt;
                        return BootPerf.measure(&#039;deferred nations-era flag image prewarm&#039;, {&lt;br /&gt;
                            era: era,&lt;br /&gt;
                            count: flagUrls.length,&lt;br /&gt;
                            limit: task.flagLimit == null ? 0 : Number(task.flagLimit)&lt;br /&gt;
                        }, function () {&lt;br /&gt;
                            return prewarmImages(flagUrls, task.flagLimit == null ? 0 : Number(task.flagLimit));&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                return results;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareGlobeSharedAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;globe-shared url resolve&#039;, { count: refs.length, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            /*&lt;br /&gt;
             * Globe shared assets are heavy 6K texture sources.  The live&lt;br /&gt;
             * NationsGlobe ready contract already waits for the actual Three.js&lt;br /&gt;
             * texture path, so decoding the same images here only duplicates work&lt;br /&gt;
             * and holds the boot screen.  Keep revision-aware URL resolution in the&lt;br /&gt;
             * entry ledger, but make the image warm-up non-blocking.&lt;br /&gt;
             */&lt;br /&gt;
            BootPerf.mark(&#039;globe-shared image prewarm skipped&#039;, {&lt;br /&gt;
                count: urls.length,&lt;br /&gt;
                level: level,&lt;br /&gt;
                reason: &#039;deferred-to-nations-globe-ready-contract&#039;&lt;br /&gt;
            });&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (!window.EntryStore || typeof window.EntryStore.preloadImages !== &#039;function&#039;) return;&lt;br /&gt;
                window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 4)),&lt;br /&gt;
                    persistent: false&lt;br /&gt;
                }).catch(function () {});&lt;br /&gt;
            }, 0);&lt;br /&gt;
            return urls;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareImageAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        var full = level === &#039;full&#039;;&lt;br /&gt;
        var exposeAs = String(task &amp;amp;&amp;amp; task.exposeAs || &#039;&#039;).trim();&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;image-assets url resolve&#039;, { count: refs.length, exposeAs: exposeAs, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            if (exposeAs === &#039;nationsGlobeLoadingGif&#039;) {&lt;br /&gt;
                window.NationsGlobeLoadingGifRef = refs[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifUrl = urls[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifFile = &#039;Gfx-vhs-glitch-001.gif&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (!full) return urls;&lt;br /&gt;
            return BootPerf.measure(&#039;image-assets image prewarm&#039;, { count: urls.length, exposeAs: exposeAs, limit: task.imageLimit == null ? 0 : Number(task.imageLimit), concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2)) }, function () {&lt;br /&gt;
                return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2))&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () { return urls; });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareHtmlEntry(task) {&lt;br /&gt;
        var url = String(task &amp;amp;&amp;amp; (task.url || task.ref) || &#039;&#039;).trim();&lt;br /&gt;
        if (!url) return Promise.resolve(null);&lt;br /&gt;
        return window.EntryStore.fetchTextUrl(url, { noStore: !!task.noStore, resourceRef: task.resourceRef || task.ref || task.title || &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareTask(task, defaultLevel) {&lt;br /&gt;
        var level = String(task &amp;amp;&amp;amp; (task.level || defaultLevel) || &#039;half&#039;).toLowerCase();&lt;br /&gt;
        var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
        if (!task || typeof task !== &#039;object&#039;) return Promise.resolve(null);&lt;br /&gt;
        if (type === &#039;html&#039;) return prepareHtmlEntry(task);&lt;br /&gt;
        if (type === &#039;json&#039;) return window.EntryStore.fetchJsonRef(task.ref, { noStore: !!task.noStore });&lt;br /&gt;
        if (type === &#039;pixel-json&#039;) {&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (level === &#039;full&#039; &amp;amp;&amp;amp; runtime &amp;amp;&amp;amp; typeof runtime.preparePixelCanvas === &#039;function&#039;) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(task.ref || task.asset);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(task.ref || task.asset);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        if (type === &#039;decorations&#039;) return prepareDecorationSet(task, level);&lt;br /&gt;
        if (type === &#039;nations-era&#039;) return prepareNationsEra(task, level);&lt;br /&gt;
        if (type === &#039;globe-shared-assets&#039;) return prepareGlobeSharedAssets(task, level);&lt;br /&gt;
        if (type === &#039;image-assets&#039;) return prepareImageAssets(task, level);&lt;br /&gt;
        return Promise.resolve(null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifest() {&lt;br /&gt;
        return BootPerf.measure(&#039;revision manifest load&#039;, {}, function () {&lt;br /&gt;
            return (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.load === &#039;function&#039; ? window.RevisionManifest.load() : Promise.resolve(null));&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;entry manifest json fetch&#039;, { ref: MANIFEST_TITLE }, function () {&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(MANIFEST_TITLE, { noStore: false });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (manifest) {&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039; || !manifest.version) {&lt;br /&gt;
                BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: &#039;invalid manifest&#039; });&lt;br /&gt;
                return defaultManifest;&lt;br /&gt;
            }&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest ready&#039;, { version: manifest.version });&lt;br /&gt;
            return manifest;&lt;br /&gt;
        }).catch(function (err) {&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: err &amp;amp;&amp;amp; (err.message || String(err)) || &#039;load failed&#039; });&lt;br /&gt;
            return defaultManifest;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function flattenInitialTasks(manifest) {&lt;br /&gt;
        var initial = manifest &amp;amp;&amp;amp; manifest.initial ? manifest.initial : {};&lt;br /&gt;
        var full = Array.isArray(initial.full) ? initial.full : [];&lt;br /&gt;
        var half = Array.isArray(initial.half) ? initial.half : [];&lt;br /&gt;
        var tasks = [];&lt;br /&gt;
        full.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;full&#039;;&lt;br /&gt;
            task.blocking = true;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        half.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;half&#039;;&lt;br /&gt;
            task.blocking = false;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        return tasks;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function flattenWarmInitialTasks(tasks) {&lt;br /&gt;
        var warmed = [];&lt;br /&gt;
        (tasks || []).forEach(function (task) {&lt;br /&gt;
            var copy;&lt;br /&gt;
            var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
            if (!task || task.blocking === false) return;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Warm boot fast path — 20260708.&lt;br /&gt;
             *&lt;br /&gt;
             * Cold boot intentionally performs the expensive work: resolving every flag,&lt;br /&gt;
             * downloading/decoding images, preparing decoration canvases, and warming shared&lt;br /&gt;
             * globe textures.  Once a tab has certified the pack against the current revision&lt;br /&gt;
             * manifest, later tabs must not replay that full workload.  They only hydrate the&lt;br /&gt;
             * small tables needed by the live page and let already-cached image/blob data be&lt;br /&gt;
             * consumed on demand.  This is the missing layer that made second tabs feel almost&lt;br /&gt;
             * as slow as first tabs even though the string checks were true.&lt;br /&gt;
             */&lt;br /&gt;
            if (type === &#039;globe-shared-assets&#039;) return;&lt;br /&gt;
            if (type === &#039;image-assets&#039;) return;&lt;br /&gt;
            copy = Object.assign({}, task);&lt;br /&gt;
            if (type === &#039;nations-era&#039;) copy.level = &#039;warm&#039;;&lt;br /&gt;
            else if (type === &#039;decorations&#039;) {&lt;br /&gt;
                copy.level = &#039;half&#039;;&lt;br /&gt;
                copy.preparePixels = false;&lt;br /&gt;
            } else if (type === &#039;pixel-json&#039;) copy.level = &#039;half&#039;;&lt;br /&gt;
            warmed.push(copy);&lt;br /&gt;
        });&lt;br /&gt;
        return warmed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackKey(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        return &#039;initial-entry-full:&#039; + version;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackToken(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        var revToken = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(version) : version;&lt;br /&gt;
        return revToken;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isInitialPackWarm(manifest) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        return !!(window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.packReady === &#039;function&#039; &amp;amp;&amp;amp; window.EntryCache.packReady(key, token));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function markInitialPackWarm(manifest, meta) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        if (window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.setPackReady === &#039;function&#039;) {&lt;br /&gt;
            window.EntryCache.setPackReady(key, token, Object.assign({ manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || BUILD_ID }, meta || {}));&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function waitMs(ms) {&lt;br /&gt;
        return new Promise(function (resolve) { window.setTimeout(resolve, Math.max(0, ms || 0)); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForBody() {&lt;br /&gt;
        if (document.body) return Promise.resolve(document.body);&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                if (document.body) return resolve(document.body);&lt;br /&gt;
                window.setTimeout(tick, 10);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDomReady() {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) return Promise.resolve();&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, resolve, { once: true });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForAnimationFrames(count) {&lt;br /&gt;
        count = Math.max(1, Math.round(count || 1));&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function next(left) {&lt;br /&gt;
                if (left &amp;lt;= 0) return resolve();&lt;br /&gt;
                window.requestAnimationFrame(function () { next(left - 1); });&lt;br /&gt;
            }&lt;br /&gt;
            next(count);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitUntil(predicate, options) {&lt;br /&gt;
        var started = now();&lt;br /&gt;
        var timeout = options &amp;amp;&amp;amp; options.timeoutMs ? options.timeoutMs : 8000;&lt;br /&gt;
        var interval = options &amp;amp;&amp;amp; options.intervalMs ? options.intervalMs : 60;&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var result = false;&lt;br /&gt;
                try { result = predicate(); } catch (err) { result = false; }&lt;br /&gt;
                if (result) return resolve({ ok: true, value: result });&lt;br /&gt;
                if (now() - started &amp;gt;= timeout) return resolve({ ok: false, timeout: true });&lt;br /&gt;
                window.setTimeout(tick, interval);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForTrackedScripts() {&lt;br /&gt;
        var list = (window.EntryScriptLoads || []).slice();&lt;br /&gt;
        if (!list.length) return Promise.resolve([]);&lt;br /&gt;
        return Promise.all(list.map(function (promise) {&lt;br /&gt;
            return Promise.resolve(promise).catch(function (err) { return { ok: false, error: err }; });&lt;br /&gt;
        }));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDocumentSurface() {&lt;br /&gt;
        return waitForDomReady().then(function () {&lt;br /&gt;
            return waitForBody();&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                return document.querySelector(&#039;.content-wrapper&#039;) &amp;amp;&amp;amp; document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
            }, { timeoutMs: 8000, intervalMs: 50 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isNationsPageSurface() {&lt;br /&gt;
        var page = String(mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
        return !!document.querySelector(&#039;.clbi-nations-panel-stack&#039;) || /(?:^|[_ ])시대(?:$|[_ ])/.test(page) || /(?:^|[_ ])Era(?:$|[_ ])/i.test(page);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getInitialNationsEra(manifest) {&lt;br /&gt;
        var tasks = flattenInitialTasks(manifest);&lt;br /&gt;
        var i;&lt;br /&gt;
        for (i = 0; i &amp;lt; tasks.length; i += 1) {&lt;br /&gt;
            if (tasks[i] &amp;amp;&amp;amp; tasks[i].type === &#039;nations-era&#039; &amp;amp;&amp;amp; String(tasks[i].level || &#039;&#039;).toLowerCase() === &#039;full&#039;) {&lt;br /&gt;
                return String(tasks[i].era || &#039;1950&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;1950&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsPanelReady(era) {&lt;br /&gt;
        return BootPerf.measure(&#039;wait nations panel api&#039;, { era: era }, function () {&lt;br /&gt;
            return waitUntil(function () { return window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.whenEraReady === &#039;function&#039;; }, {&lt;br /&gt;
                timeoutMs: 8000,&lt;br /&gt;
                intervalMs: 50&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (result) {&lt;br /&gt;
            if (!result.ok || !window.NationsPanel || typeof window.NationsPanel.whenEraReady !== &#039;function&#039;) return null;&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel era ready&#039;, { era: era }, function () {&lt;br /&gt;
                return window.NationsPanel.whenEraReady(era, { timeoutMs: 15000 }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel dom ready&#039;, { era: era }, function () {&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var panel = document.querySelector(&#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + era + &#039;&amp;quot;] .clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;) ||&lt;br /&gt;
                        document.querySelector(&#039;.clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
                    return panel &amp;amp;&amp;amp; panel.classList.contains(&#039;clbi-nations-list-json-ready&#039;) &amp;amp;&amp;amp; panel.getAttribute(&#039;data-nation-list-year-loaded&#039;) === String(era);&lt;br /&gt;
                }, { timeoutMs: 15000, intervalMs: 80 });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsGlobeReady(options) {&lt;br /&gt;
        var globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
        var soft;&lt;br /&gt;
        var timeout;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        soft = !!options.soft;&lt;br /&gt;
        timeout = Number(options.timeoutMs || (soft ? 450 : 30000));&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            BootPerf.mark(&#039;wait nations globe skipped&#039;, { reason: &#039;no globe node&#039;, soft: soft });&lt;br /&gt;
            return Promise.resolve(null);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return BootPerf.measure(soft ? &#039;wait nations globe warm attach&#039; : &#039;wait nations globe ready contract&#039;, { soft: soft, timeoutMs: timeout }, function () {&lt;br /&gt;
            if (soft) {&lt;br /&gt;
                /*&lt;br /&gt;
                Warm-tab policy:&lt;br /&gt;
                Raw globe images are already revision-checked and blob-cached by EntryCache,&lt;br /&gt;
                but WebGL scene creation, GPU upload, topojson parsing, and per-tab Three.js&lt;br /&gt;
                objects cannot be shared across browser tabs.  Waiting for the full is-ready&lt;br /&gt;
                contract here makes a warm tab slower than the first tab.  In warm mode the&lt;br /&gt;
                initial gate only waits until the globe consumer has been attached, then the&lt;br /&gt;
                globe finishes its own per-tab hydrate without holding the whole page hostage.&lt;br /&gt;
                */&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                    if (!node) return false;&lt;br /&gt;
                    return node.getAttribute(&#039;data-nations-globe-ready&#039;) === &#039;1&#039; ||&lt;br /&gt;
                        !!node.CLBI_NationsGlobeInstance ||&lt;br /&gt;
                        !!node.querySelector(&#039;.clbi-nations-globe-stage&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;is-ready&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;has-error&#039;);&lt;br /&gt;
                }, { timeoutMs: timeout, intervalMs: 40 });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                return node &amp;amp;&amp;amp; (node.classList.contains(&#039;is-ready&#039;) || node.classList.contains(&#039;has-error&#039;));&lt;br /&gt;
            }, { timeoutMs: timeout, intervalMs: 100 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationsReady(options) {&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
            if (!runtime) return null;&lt;br /&gt;
            if (options.warm) {&lt;br /&gt;
                /*&lt;br /&gt;
                 * Warm tab contract — 20260708.&lt;br /&gt;
                 *&lt;br /&gt;
                 * The decoration registry and pixel canvases were prepared by the cold tab.&lt;br /&gt;
                 * Calling reload() here re-reads and re-syncs the decoration layer during the&lt;br /&gt;
                 * boot gate, which is visible as a pointless delay.  A warm tab only needs the&lt;br /&gt;
                 * current visibility pass so boot can release quickly and editing/preview tools&lt;br /&gt;
                 * remain responsive.&lt;br /&gt;
                 */&lt;br /&gt;
                try {&lt;br /&gt;
                    if (typeof runtime.updateVisibility === &#039;function&#039;) runtime.updateVisibility(document);&lt;br /&gt;
                    else if (typeof runtime.sync === &#039;function&#039;) runtime.sync();&lt;br /&gt;
                } catch (err) {}&lt;br /&gt;
                return waitForAnimationFrames(1);&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof runtime.reload !== &#039;function&#039;) return null;&lt;br /&gt;
            return runtime.reload().catch(function () { return null; }).then(function () {&lt;br /&gt;
                return waitForAnimationFrames(2);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForCurrentEntrySurface(manifest, options) {&lt;br /&gt;
        var era = getInitialNationsEra(manifest);&lt;br /&gt;
        var warmPack;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        warmPack = !!options.warmPack;&lt;br /&gt;
&lt;br /&gt;
        if (!isNationsPageSurface()) {&lt;br /&gt;
            return waitForDecorationsReady({ warm: !!warmPack }).then(function () { return waitForAnimationFrames(1); });&lt;br /&gt;
        }&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = warmPack ? &#039;Hydrating current information surface from warm cache&#039; : &#039;Preparing current information surface&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for era &#039; + era + &#039; ready contract&#039;;&lt;br /&gt;
        return waitForNationsPanelReady(era)&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (warmPack) {&lt;br /&gt;
                    if (bootDetailNode) bootDetailNode.textContent = &#039;attaching globe warm consumer&#039;;&lt;br /&gt;
                    return waitForNationsGlobeReady({ soft: true, timeoutMs: 450 });&lt;br /&gt;
                }&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for globe ready contract&#039;;&lt;br /&gt;
                return waitForNationsGlobeReady();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for decoration surface&#039;;&lt;br /&gt;
                return waitForDecorationsReady({ warm: warmPack });&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () { return waitForAnimationFrames(warmPack ? 1 : 2); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runDeferredEntryWarmups() {&lt;br /&gt;
        var queue = deferredEntryWarmups.splice(0);&lt;br /&gt;
        if (!queue.length) return;&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            var chain = Promise.resolve();&lt;br /&gt;
            queue.forEach(function (job) {&lt;br /&gt;
                chain = chain.then(function () {&lt;br /&gt;
                    try { return job(); }&lt;br /&gt;
                    catch (err) { return null; }&lt;br /&gt;
                }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }, 1200);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runInitialLoad(options) {&lt;br /&gt;
        var done = 0;&lt;br /&gt;
        var manifestRef;&lt;br /&gt;
        var tasks;&lt;br /&gt;
        var bootOptions;&lt;br /&gt;
        var timedOut = false;&lt;br /&gt;
        var warmPack = false;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        bootStartTime = window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.startTime ? window.__BootGatePrelude.startTime : now();&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        ensureBootScreen();&lt;br /&gt;
        updateBootProgress(0, 1, &#039;loading entry manifest&#039;);&lt;br /&gt;
&lt;br /&gt;
        manifestRef = loadManifest().then(function (manifest) {&lt;br /&gt;
            var minDisplay;&lt;br /&gt;
            var totalSteps;&lt;br /&gt;
            var maxBlockingMs;&lt;br /&gt;
            var timeoutHandle;&lt;br /&gt;
            bootOptions = manifest.boot || {};&lt;br /&gt;
            warmPack = isInitialPackWarm(manifest);&lt;br /&gt;
            minDisplay = options.minDisplayMs || (warmPack ? (bootOptions.cachedMinDisplayMs || 220) : (bootOptions.minDisplayMs || 950));&lt;br /&gt;
            tasks = flattenInitialTasks(manifest);&lt;br /&gt;
            if (!tasks.length) tasks = flattenInitialTasks(defaultManifest);&lt;br /&gt;
            if (warmPack) tasks = flattenWarmInitialTasks(tasks);&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            Full/half contract:&lt;br /&gt;
            These manifest tasks only prepare data assets.  The gate is not allowed to open&lt;br /&gt;
            until the current page surface has consumed that data and reported a real ready&lt;br /&gt;
            state below.  Do not reintroduce localStorage-only skip logic here; a cached&lt;br /&gt;
            version number is not the same as current-tab readiness.&lt;br /&gt;
            */&lt;br /&gt;
            totalSteps = tasks.length + 4;&lt;br /&gt;
            updateBootProgress(0, totalSteps, warmPack ? &#039;hydrating warm entry cache&#039; : &#039;starting entry packs&#039;);&lt;br /&gt;
&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                maxBlockingMs = Number(options.maxBlockingMs || bootOptions.maxBlockingMs || 30000);&lt;br /&gt;
                timeoutHandle = window.setTimeout(function () {&lt;br /&gt;
                    timedOut = true;&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }, maxBlockingMs);&lt;br /&gt;
&lt;br /&gt;
                Promise.all(tasks.map(function (task) {&lt;br /&gt;
                    var label = task.label || task.id || task.type || &#039;entry task&#039;;&lt;br /&gt;
                    return BootPerf.measure(&#039;boot task: &#039; + label, { id: task.id || &#039;&#039;, type: task.type || &#039;&#039;, level: task.level || task.level === 0 ? task.level : &#039;&#039; }, function () {&lt;br /&gt;
                        return prepareTask(task, task.level);&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }).then(function () {&lt;br /&gt;
                        done += 1;&lt;br /&gt;
                        updateBootProgress(done, totalSteps, label);&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;loading subsystem scripts&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait tracked subsystem scripts&#039;, { count: (window.EntryScriptLoads || []).length }, function () { return waitForTrackedScripts(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for document shell&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait document shell&#039;, {}, function () { return waitForDocumentSurface(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for current entry surface&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait current entry surface&#039;, { warmPack: warmPack }, function () { return waitForCurrentEntrySurface(manifest, { warmPack: warmPack }); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(totalSteps, totalSteps, warmPack ? &#039;warm entry surface complete&#039; : &#039;entry surface complete&#039;);&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                var elapsed = now() - bootStartTime;&lt;br /&gt;
                var wait = Math.max(0, minDisplay - elapsed);&lt;br /&gt;
                return waitMs(wait);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                localStorage.setItem(READY_KEY, String(manifest.version));&lt;br /&gt;
                if (!timedOut) markInitialPackWarm(manifest, { warmSource: warmPack ? &#039;cache-hit&#039; : &#039;cold-build&#039; });&lt;br /&gt;
                if (bootStatusNode) bootStatusNode.textContent = timedOut ? &#039;Entry surface ready with deferred items&#039; : (warmPack ? &#039;Entry surface ready from warm cache&#039; : &#039;Entry surface ready&#039;);&lt;br /&gt;
                updateBootProgress(tasks.length + 4, tasks.length + 4, timedOut ? &#039;timeout: continuing deferred loading&#039; : (warmPack ? &#039;warm cache complete&#039; : &#039;complete&#039;));&lt;br /&gt;
                BootPerf.mark(&#039;boot gate complete&#039;, { timedOut: timedOut, warmPack: warmPack, manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || &#039;&#039; });&lt;br /&gt;
                BootPerf.print();&lt;br /&gt;
                runDeferredEntryWarmups();&lt;br /&gt;
                window.setTimeout(function () {&lt;br /&gt;
                    if (requiresLoginGate()) showLoginGate();&lt;br /&gt;
                    else hideBootScreen();&lt;br /&gt;
                }, 120);&lt;br /&gt;
                return manifest;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return manifestRef;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startBoot(options) {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return Promise.resolve({ skipped: true, reason: &#039;developer-or-editing-page&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        if (hasBootParam(&#039;0&#039;) &amp;amp;&amp;amp; !requiresLoginGate() &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return Promise.resolve(null);&lt;br /&gt;
        if (bootStarted &amp;amp;&amp;amp; bootPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return bootPromise;&lt;br /&gt;
        bootStarted = true;&lt;br /&gt;
        bootPromise = runInitialLoad(options || {});&lt;br /&gt;
        return bootPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resetBoot() {&lt;br /&gt;
        localStorage.removeItem(READY_KEY);&lt;br /&gt;
        bootStarted = false;&lt;br /&gt;
        bootPromise = null;&lt;br /&gt;
        loginGateLocked = false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    BootGate intentionally has no SPA hold/release API.&lt;br /&gt;
    Initial boot is the only blocking phase; later SPA routes must consume prepared&lt;br /&gt;
    EntryStore artifacts without reopening the loading surface.&lt;br /&gt;
    */&lt;br /&gt;
&lt;br /&gt;
    function buildBootReport() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD_ID,&lt;br /&gt;
            state: {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                excludedPage: BOOT_EXCLUDED_PAGE,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;))&lt;br /&gt;
            },&lt;br /&gt;
            store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
            bootPerf: window.BootPerf &amp;amp;&amp;amp; typeof window.BootPerf.summary === &#039;function&#039; ? window.BootPerf.summary() : null,&lt;br /&gt;
            revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null,&lt;br /&gt;
            nations: window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.diagnostics === &#039;function&#039; ? window.NationsPanel.diagnostics() : null,&lt;br /&gt;
            decorations: window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.diagnostics === &#039;function&#039; ? window.Decorations.diagnostics() : null&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryLoader = window.EntryLoader || {&lt;br /&gt;
        loadManifest: loadManifest,&lt;br /&gt;
        prepareTask: prepareTask,&lt;br /&gt;
        prepareNationsEra: prepareNationsEra,&lt;br /&gt;
        prepareDecorationSet: prepareDecorationSet,&lt;br /&gt;
        prepareGlobeSharedAssets: prepareGlobeSharedAssets,&lt;br /&gt;
        prepareImageAssets: prepareImageAssets,&lt;br /&gt;
        prepareHtmlEntry: prepareHtmlEntry,&lt;br /&gt;
        runInitialLoad: runInitialLoad&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function showBootPreview(options) {&lt;br /&gt;
        var node;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        /*&lt;br /&gt;
         * Boot preview is a design/editing surface, not a real boot gate.&lt;br /&gt;
         * The earlier preview reused activateBootSurface(), which applied&lt;br /&gt;
         * html.boot-gate-active and hid the entire wiki shell, including&lt;br /&gt;
         * DevTools.  That made it impossible to edit loading-screen&lt;br /&gt;
         * decorations while previewing them.  Keep the real first-entry gate&lt;br /&gt;
         * full-screen, but make preview a small non-blocking surface below the&lt;br /&gt;
         * DevTools z-index so the owner can keep using the editor.&lt;br /&gt;
         */&lt;br /&gt;
        node = ensureBootScreen({ preview: true });&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        node.classList.add(&#039;is-preview&#039;);&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = options.status || &#039;Loading screen preview&#039;;&lt;br /&gt;
        updateBootProgress(Number(options.progress || 64), 100, options.detail || &#039;preview mode: no entry tasks are running&#039;);&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BootGate = window.BootGate || {&lt;br /&gt;
        version: BUILD_ID,&lt;br /&gt;
        start: startBoot,&lt;br /&gt;
        show: function (options) {&lt;br /&gt;
            options = options || {};&lt;br /&gt;
            options.force = true;&lt;br /&gt;
            resetBoot();&lt;br /&gt;
            return startBoot(options);&lt;br /&gt;
        },&lt;br /&gt;
        reset: resetBoot,&lt;br /&gt;
        hide: hideBootScreen,&lt;br /&gt;
        preview: showBootPreview,&lt;br /&gt;
        report: buildBootReport,&lt;br /&gt;
        state: function () {&lt;br /&gt;
            return {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                prelude: !!window.__BootGatePrelude,&lt;br /&gt;
                store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
                revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function prime() {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        waitForBody().then(function () {&lt;br /&gt;
            ensureBootScreen();&lt;br /&gt;
            return waitForAnimationFrames(1);&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            startBoot();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    waitForBody().then(function () {&lt;br /&gt;
        if (!BOOT_EXCLUDED_PAGE) ensureBootScreen();&lt;br /&gt;
        else hideBootScreen();&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(prime, 0);&lt;br /&gt;
})(window, document, window.mediaWiki || window.mw);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:DevTools.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsPanel.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsGlobe.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* CLBI safety guard: adaptive reset functions must exist before shell metric callbacks run. */&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.resetLeftRecentAdaptiveState = resetLeftRecentAdaptiveState;&lt;br /&gt;
window.resetLeftBillboardAdaptiveState = resetLeftBillboardAdaptiveState;&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:AnecdoteViewer.js&#039;);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SYSTEM_TITLE_NAMESPACES = {&lt;br /&gt;
        &#039;-1&#039;: true,&lt;br /&gt;
        &#039;4&#039;: true,&lt;br /&gt;
        &#039;5&#039;: true,&lt;br /&gt;
        &#039;6&#039;: true,&lt;br /&gt;
        &#039;7&#039;: true,&lt;br /&gt;
        &#039;8&#039;: true,&lt;br /&gt;
        &#039;9&#039;: true,&lt;br /&gt;
        &#039;10&#039;: true,&lt;br /&gt;
        &#039;11&#039;: true,&lt;br /&gt;
        &#039;12&#039;: true,&lt;br /&gt;
        &#039;13&#039;: true,&lt;br /&gt;
        &#039;14&#039;: true,&lt;br /&gt;
        &#039;15&#039;: true,&lt;br /&gt;
        &#039;828&#039;: true,&lt;br /&gt;
        &#039;829&#039;: true&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function normalizePageNameForShell(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readCurrentPageNameForShell() {&lt;br /&gt;
        var pageName = mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (pageName) {&lt;br /&gt;
            return normalizePageNameForShell(pageName);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return normalizePageNameForShell(window.location.pathname || &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnecdoteNamespaceForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var canonicalNamespace = String(mw.config.get(&#039;wgCanonicalNamespace&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 3000 ||&lt;br /&gt;
            canonicalNamespace === &#039;anecdote&#039; ||&lt;br /&gt;
            /^(anecdote|에넥도트):/i.test(pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBackendOrSystemPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var action = String(mw.config.get(&#039;wgAction&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var lowerPageName = pageName.toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === &#039;대문&#039;) {&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (SYSTEM_TITLE_NAMESPACES[String(namespaceNumber)]) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;json&#039; || contentModel === &#039;sanitized-css&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/\.(css|js|json)$/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/^(mediawiki|미디어위키|special|특수):/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMediaWikiSystemAssetPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 8 &amp;amp;&amp;amp;&lt;br /&gt;
            (/\.(css|js)$/i.test(pageName) || contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;sanitized-css&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var systemDocRawFetchToken = 0;&lt;br /&gt;
&lt;br /&gt;
    function cleanupLegacySystemDocCodeMutationsForShell() {&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codepane&#039;).forEach(function (pane) {&lt;br /&gt;
            var parent;&lt;br /&gt;
&lt;br /&gt;
            if (!pane || !pane.parentNode) return;&lt;br /&gt;
&lt;br /&gt;
            parent = pane.parentNode;&lt;br /&gt;
            while (pane.firstChild) {&lt;br /&gt;
                parent.insertBefore(pane.firstChild, pane);&lt;br /&gt;
            }&lt;br /&gt;
            parent.removeChild(pane);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codebox&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;style&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocOutputForShell() {&lt;br /&gt;
        return document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findSystemDocSourceNodeForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var children;&lt;br /&gt;
        var preferred;&lt;br /&gt;
&lt;br /&gt;
        if (!output) return null;&lt;br /&gt;
&lt;br /&gt;
        children = Array.prototype.slice.call(output.children || [])&lt;br /&gt;
            .filter(function (el) {&lt;br /&gt;
                return el &amp;amp;&amp;amp; el.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-doc-indicator-row&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-source-viewer&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    !el.classList.contains(&#039;catlinks&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    (el.textContent || &#039;&#039;).trim().length &amp;gt; 200;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
        preferred = children.filter(function (el) {&lt;br /&gt;
            return el.matches &amp;amp;&amp;amp; el.matches(&#039;.mw-highlight, .mw-code, pre&#039;);&lt;br /&gt;
        })[0];&lt;br /&gt;
&lt;br /&gt;
        return preferred || children.sort(function (a, b) {&lt;br /&gt;
            return (b.textContent || &#039;&#039;).trim().length - (a.textContent || &#039;&#039;).trim().length;&lt;br /&gt;
        })[0] || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocRawUrlForShell() {&lt;br /&gt;
        var title = mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell();&lt;br /&gt;
        var url;&lt;br /&gt;
&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            url = mw.util.getUrl(title, {&lt;br /&gt;
                action: &#039;raw&#039;,&lt;br /&gt;
                ctype: &#039;text/plain&#039;&lt;br /&gt;
            });&lt;br /&gt;
        } else {&lt;br /&gt;
            url = &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/plain&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, title);&lt;br /&gt;
        }&lt;br /&gt;
        return url;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (viewer &amp;amp;&amp;amp; viewer.parentNode) {&lt;br /&gt;
            viewer.parentNode.removeChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-original-source-hidden&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-source-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureSystemDocSourceViewerForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var source;&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var fallbackText;&lt;br /&gt;
&lt;br /&gt;
        if (!output || !isMediaWikiSystemAssetPageForShell()) return null;&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
&lt;br /&gt;
        source = findSystemDocSourceNodeForShell();&lt;br /&gt;
        if (!source) return null;&lt;br /&gt;
&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!viewer) {&lt;br /&gt;
            viewer = document.createElement(&#039;pre&#039;);&lt;br /&gt;
            viewer.id = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            viewer.className = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            output.appendChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fallbackText = source.textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!viewer.textContent &amp;amp;&amp;amp; fallbackText) {&lt;br /&gt;
            viewer.textContent = fallbackText;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        source.classList.add(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
        source.setAttribute(&#039;data-clbi-system-source-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        source.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
        return viewer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var token;&lt;br /&gt;
        var currentScrollTop;&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = String(mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell());&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        시스템 문서 뷰어가 이미 만들어져 있고 raw 원문도 로드된 상태라면&lt;br /&gt;
        다시 source 탐색/숨김/스타일 재적용을 하지 않는다.&lt;br /&gt;
        DevTools Elements 패널에서 body가 계속 파랗게 깜빡이던 원인은&lt;br /&gt;
        MutationObserver가 이 재적용을 반복해서 DOM attribute mutation을 만들었기 때문이다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            viewer &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-title&#039;) === pageName &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-loaded&#039;) === &#039;1&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        viewer = ensureSystemDocSourceViewerForShell();&lt;br /&gt;
        if (!viewer) return;&lt;br /&gt;
&lt;br /&gt;
        currentScrollTop = viewer.scrollTop || 0;&lt;br /&gt;
        viewer.setAttribute(&#039;data-clbi-raw-title&#039;, pageName);&lt;br /&gt;
        token = ++systemDocRawFetchToken;&lt;br /&gt;
&lt;br /&gt;
        fetch(getSystemDocRawUrlForShell(), { credentials: &#039;same-origin&#039; })&lt;br /&gt;
            .then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;raw fetch failed &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (token !== systemDocRawFetchToken) return;&lt;br /&gt;
&lt;br /&gt;
                currentScrollTop = viewer.scrollTop || currentScrollTop || 0;&lt;br /&gt;
&lt;br /&gt;
                if (text &amp;amp;&amp;amp; viewer.textContent !== text) {&lt;br /&gt;
                    viewer.textContent = text;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;1&#039;);&lt;br /&gt;
                viewer.scrollTop = currentScrollTop;&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;0&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocIndicatorForShell() {&lt;br /&gt;
        var existing = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            document.body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (existing &amp;amp;&amp;amp; existing.parentNode) {&lt;br /&gt;
            existing.parentNode.removeChild(existing);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        removeSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocIndicatorForShell() {&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var extMatch;&lt;br /&gt;
        var ext;&lt;br /&gt;
        var row;&lt;br /&gt;
        var box;&lt;br /&gt;
        var meta;&lt;br /&gt;
        var label;&lt;br /&gt;
        var type;&lt;br /&gt;
        var title;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var main;&lt;br /&gt;
&lt;br /&gt;
        if (!document.body || !isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = readCurrentPageNameForShell();&lt;br /&gt;
        extMatch = pageName.match(/\.(css|js)$/i);&lt;br /&gt;
        ext = extMatch ? extMatch[1].toUpperCase() : &#039;DOC&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.classList.add(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        row = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!row) {&lt;br /&gt;
            row = document.createElement(&#039;div&#039;);&lt;br /&gt;
            row.id = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
            row.className = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
&lt;br /&gt;
            box = document.createElement(&#039;div&#039;);&lt;br /&gt;
            box.className = &#039;clbi-system-doc-indicator&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta = document.createElement(&#039;div&#039;);&lt;br /&gt;
            meta.className = &#039;clbi-system-doc-meta&#039;;&lt;br /&gt;
&lt;br /&gt;
            label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            label.className = &#039;clbi-system-doc-label&#039;;&lt;br /&gt;
            label.textContent = &#039;SYSTEM DOCUMENT&#039;;&lt;br /&gt;
&lt;br /&gt;
            type = document.createElement(&#039;span&#039;);&lt;br /&gt;
            type.className = &#039;clbi-system-doc-type&#039;;&lt;br /&gt;
&lt;br /&gt;
            title = document.createElement(&#039;div&#039;);&lt;br /&gt;
            title.className = &#039;clbi-system-doc-title&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta.appendChild(label);&lt;br /&gt;
            meta.appendChild(type);&lt;br /&gt;
            box.appendChild(meta);&lt;br /&gt;
            box.appendChild(title);&lt;br /&gt;
            row.appendChild(box);&lt;br /&gt;
&lt;br /&gt;
            anchor = getSystemDocOutputForShell();&lt;br /&gt;
            main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (anchor &amp;amp;&amp;amp; anchor.parentNode) {&lt;br /&gt;
                anchor.parentNode.insertBefore(row, anchor);&lt;br /&gt;
            } else if (main) {&lt;br /&gt;
                main.insertBefore(row, main.firstChild);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        type = row.querySelector(&#039;.clbi-system-doc-type&#039;);&lt;br /&gt;
        title = row.querySelector(&#039;.clbi-system-doc-title&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (type) type.textContent = ext;&lt;br /&gt;
        if (title) title.textContent = pageName;&lt;br /&gt;
&lt;br /&gt;
        renderSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var PAGE_TITLE_TARGET_SELECTORS = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var pageShellObserverStarted = false;&lt;br /&gt;
    var pageShellObserverTimer = null;&lt;br /&gt;
&lt;br /&gt;
    function setPageTitleDomHidden(hidden) {&lt;br /&gt;
        var nodes = document.querySelectorAll(PAGE_TITLE_TARGET_SELECTORS.join(&#039;,&#039;));&lt;br /&gt;
&lt;br /&gt;
        nodes.forEach(function (node) {&lt;br /&gt;
            if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
            if (hidden) {&lt;br /&gt;
                node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
                node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
                node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClasses() {&lt;br /&gt;
        var body = document.body;&lt;br /&gt;
        var isSystemPage;&lt;br /&gt;
&lt;br /&gt;
        if (!body) return;&lt;br /&gt;
&lt;br /&gt;
        isSystemPage = isBackendOrSystemPageForShell();&lt;br /&gt;
&lt;br /&gt;
        body.classList.remove(&#039;page-title-hidden&#039;, &#039;page-title-visible&#039;, &#039;backend-system-page&#039;, &#039;anecdote-namespace-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            removeSystemDocIndicatorForShell();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isAnecdoteNamespaceForShell()) {&lt;br /&gt;
            body.classList.add(&#039;anecdote-namespace-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;, &#039;backend-system-page&#039;, &#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
            renderSystemDocIndicatorForShell();&lt;br /&gt;
        } else if (isSystemPage) {&lt;br /&gt;
            body.classList.add(&#039;page-title-visible&#039;, &#039;backend-system-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(false);&lt;br /&gt;
        } else {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClassesDeferred() {&lt;br /&gt;
        applyPageShellClasses();&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 0);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 80);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startPageShellObserver() {&lt;br /&gt;
        var observer;&lt;br /&gt;
&lt;br /&gt;
        if (pageShellObserverStarted || !window.MutationObserver || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
        pageShellObserverStarted = true;&lt;br /&gt;
        observer = new MutationObserver(function (mutations) {&lt;br /&gt;
            var i;&lt;br /&gt;
            var target;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            시스템 CSS/JS 문서는 applyPageShellClasses()가 초기에 한 번&lt;br /&gt;
            인디케이터와 source viewer를 만든 뒤에는 MutationObserver가 다시&lt;br /&gt;
            같은 렌더링을 반복할 필요가 없다. 이 반복이 DevTools에서 body/요소가&lt;br /&gt;
            계속 플래시되는 직접 원인이다.&lt;br /&gt;
            SPA 전환 뒤의 처리는 loadPage()와 wikipage.content hook에서 따로 호출된다.&lt;br /&gt;
            */&lt;br /&gt;
            if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
                for (i = 0; i &amp;lt; mutations.length; i += 1) {&lt;br /&gt;
                    target = mutations[i] &amp;amp;&amp;amp; mutations[i].target;&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        target &amp;amp;&amp;amp;&lt;br /&gt;
                        target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                        (&lt;br /&gt;
                            target.id === &#039;clbi-system-source-viewer&#039; ||&lt;br /&gt;
                            target.id === &#039;clbi-system-doc-indicator-row&#039; ||&lt;br /&gt;
                            (target.closest &amp;amp;&amp;amp; target.closest(&#039;#clbi-system-source-viewer, #clbi-system-doc-indicator-row&#039;))&lt;br /&gt;
                        )&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-doc-indicator-row&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-source-viewer&#039;)&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (pageShellObserverTimer) return;&lt;br /&gt;
&lt;br /&gt;
            pageShellObserverTimer = window.setTimeout(function () {&lt;br /&gt;
                pageShellObserverTimer = null;&lt;br /&gt;
                applyPageShellClasses();&lt;br /&gt;
            }, 50);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        observer.observe(document.body, {&lt;br /&gt;
            childList: true,&lt;br /&gt;
            subtree: true&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {&lt;br /&gt;
            applyPageShellClassesDeferred();&lt;br /&gt;
            startPageShellObserver();&lt;br /&gt;
        });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyPageShellClassesDeferred();&lt;br /&gt;
        startPageShellObserver();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(applyPageShellClassesDeferred);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_PAGE_SHELL = {&lt;br /&gt;
        refresh: applyPageShellClasses,&lt;br /&gt;
        isBackendOrSystemPage: isBackendOrSystemPageForShell,&lt;br /&gt;
        isSystemAssetPage: isMediaWikiSystemAssetPageForShell,&lt;br /&gt;
        renderSystemDocIndicator: renderSystemDocIndicatorForShell,&lt;br /&gt;
        removeSystemDocIndicator: removeSystemDocIndicatorForShell,&lt;br /&gt;
        refreshSystemDocSourceViewer: renderSystemDocSourceViewerForShell&lt;br /&gt;
    };&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
function loadLangScript(done) {&lt;br /&gt;
    $.getScript(&#039;/index.php?title=미디어위키:Lang.js&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;)&lt;br /&gt;
        .done(function() {&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        })&lt;br /&gt;
        .fail(function(a, b, c) {&lt;br /&gt;
            console.error(&#039;Lang.js load failed:&#039;, b, c);&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initHalftoneBackground() {&lt;br /&gt;
    try {&lt;br /&gt;
        initWebGLHalftoneBackground();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;WebGL halftone background failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initWebGLHalftoneBackground() {&lt;br /&gt;
    var canvasId = &#039;site-halftone-bg&#039;;&lt;br /&gt;
    var existing = document.getElementById(canvasId);&lt;br /&gt;
    var canvas = existing || document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    var halftoneState = window.SiteHalftoneBackgroundState || (window.SiteHalftoneBackgroundState = { runId: 0 });&lt;br /&gt;
    var runId = halftoneState.runId + 1;&lt;br /&gt;
&lt;br /&gt;
    halftoneState.runId = runId;&lt;br /&gt;
&lt;br /&gt;
    if (!existing) {&lt;br /&gt;
        canvas.id = canvasId;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        document.body.insertBefore(canvas, document.body.firstChild || null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canvas.style.position = &#039;fixed&#039;;&lt;br /&gt;
    canvas.style.inset = &#039;0&#039;;&lt;br /&gt;
    canvas.style.width = &#039;100vw&#039;;&lt;br /&gt;
    canvas.style.height = &#039;100vh&#039;;&lt;br /&gt;
    canvas.style.pointerEvents = &#039;none&#039;;&lt;br /&gt;
    canvas.style.background = &#039;#000000&#039;;&lt;br /&gt;
    canvas.style.display = &#039;block&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!canvas.getAttribute(&#039;data-halftone-context-watch&#039;)) {&lt;br /&gt;
        canvas.setAttribute(&#039;data-halftone-context-watch&#039;, &#039;1&#039;);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;, function (event) {&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            canvas.style.display = &#039;none&#039;;&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = true;&lt;br /&gt;
                window.SiteHalftoneBackgroundState.runId += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }, false);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextrestored&#039;, function () {&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = false;&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(initWebGLHalftoneBackground, 0);&lt;br /&gt;
        }, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gl = canvas.getContext(&#039;webgl&#039;, {&lt;br /&gt;
        alpha: false,&lt;br /&gt;
        antialias: false,&lt;br /&gt;
        depth: false,&lt;br /&gt;
        stencil: false,&lt;br /&gt;
        preserveDrawingBuffer: false,&lt;br /&gt;
        powerPreference: &#039;low-power&#039;&lt;br /&gt;
    }) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!gl) {&lt;br /&gt;
        canvas.style.display = &#039;none&#039;;&lt;br /&gt;
        console.warn(&#039;WebGL background unavailable.&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexSrc = [&lt;br /&gt;
        &#039;attribute vec2 a_position;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_position, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var fragmentSrc = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_resolution;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;const float TAU = 6.28318530718;&#039;,&lt;br /&gt;
        &#039;float gaussian(float v, float r) {&#039;,&lt;br /&gt;
        &#039;  return exp(-((v * v) / max(0.0001, r * r)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float hash(vec2 p) {&#039;,&lt;br /&gt;
        &#039;  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float bucketAlpha(float a) {&#039;,&lt;br /&gt;
        &#039;  float i = floor(a * 9.0);&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 1.0) return 0.040;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 2.0) return 0.080;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 3.0) return 0.135;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 4.0) return 0.210;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 5.0) return 0.310;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 6.0) return 0.430;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 7.0) return 0.580;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 8.0) return 0.760;&#039;,&lt;br /&gt;
        &#039;  return 0.920;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 frag = gl_FragCoord.xy;&#039;,&lt;br /&gt;
        &#039;  float spacing = 5.0;&#039;,&lt;br /&gt;
        &#039;  float dotSize = 1.08;&#039;,&lt;br /&gt;
        &#039;  vec2 grid = floor(frag / spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 inCell = mod(frag, spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 dotOrigin = vec2(1.0, 1.0);&#039;,&lt;br /&gt;
        &#039;  vec2 dotCenter = dotOrigin + vec2(dotSize * 0.5);&#039;,&lt;br /&gt;
        &#039;  vec2 local = abs(inCell - dotCenter);&#039;,&lt;br /&gt;
        &#039;  float noise = hash(grid);&#039;,&lt;br /&gt;
        &#039;  float size = dotSize + noise * 0.18;&#039;,&lt;br /&gt;
        &#039;  float dotMask = 1.0 - smoothstep(size * 0.5, size * 0.5 + 0.22, max(local.x, local.y));&#039;,&lt;br /&gt;
        &#039;  vec2 uv = frag / u_resolution;&#039;,&lt;br /&gt;
        &#039;  float centerLine = 0.50 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 1.32 + 0.08) * TAU) * 0.070 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 3.18 + 0.34) * TAU) * 0.030;&#039;,&lt;br /&gt;
        &#039;  float u = uv.x - centerLine;&#039;,&lt;br /&gt;
        &#039;  float absU = abs(u);&#039;,&lt;br /&gt;
        &#039;  float sideLift = smoothstep(0.065, 0.44, absU);&#039;,&lt;br /&gt;
        &#039;  float valley = gaussian(u, 0.150);&#039;,&lt;br /&gt;
        &#039;  float t = u_time;&#039;,&lt;br /&gt;
        &#039;  float leftRibbonCenter = -0.28 + sin((uv.y * 3.20 + 0.12) * TAU) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float rightRibbonCenter = 0.27 + sin((uv.y * 2.85 + 0.56) * TAU) * 0.055;&#039;,&lt;br /&gt;
        &#039;  float leftRibbon = gaussian(u - leftRibbonCenter, 0.105);&#039;,&lt;br /&gt;
        &#039;  float rightRibbon = gaussian(u - rightRibbonCenter, 0.110);&#039;,&lt;br /&gt;
        &#039;  float foldedU = u +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 4.40 + 0.22) * TAU) * 0.050 * (0.3 + sideLift) +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 7.20 + uv.x * 1.10) * TAU) * 0.022;&#039;,&lt;br /&gt;
        &#039;  float verticalFold = pow(0.5 + 0.5 * cos(((foldedU * 3.05) + (sin(uv.y * TAU * 2.35) * 0.18)) * TAU), 2.5);&#039;,&lt;br /&gt;
        &#039;  float diagonalFold = pow(0.5 + 0.5 * cos(((foldedU * 1.80) - (uv.y * 1.12) + 0.18) * TAU), 2.1);&#039;,&lt;br /&gt;
        &#039;  float waist = gaussian(uv.y - 0.50, 0.25) * gaussian(absU - 0.20, 0.19);&#039;,&lt;br /&gt;
        &#039;  float grain = (noise - 0.5) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float staticField =&#039;,&lt;br /&gt;
        &#039;    0.055 +&#039;,&lt;br /&gt;
        &#039;    sideLift * 0.210 +&#039;,&lt;br /&gt;
        &#039;    (leftRibbon + rightRibbon) * 0.145 +&#039;,&lt;br /&gt;
        &#039;    verticalFold * (0.055 + sideLift * 0.115) +&#039;,&lt;br /&gt;
        &#039;    diagonalFold * 0.045 +&#039;,&lt;br /&gt;
        &#039;    waist * 0.060 -&#039;,&lt;br /&gt;
        &#039;    valley * 0.150 +&#039;,&lt;br /&gt;
        &#039;    grain;&#039;,&lt;br /&gt;
        &#039;  float alpha = staticField;&#039;,&lt;br /&gt;
        &#039;  alpha += 0.115 * (leftRibbon + rightRibbon) * sin(t * 0.00030 + ((uv.y * 1.9) + sideLift * 0.4) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.095 * verticalFold * (0.4 + sideLift) * sin(t * 0.00041 + ((uv.y * 2.7) + foldedU * 0.65) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.070 * waist * sin(t * 0.00053 + ((uv.y * 3.1) - absU * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.060 * (1.0 - valley) * diagonalFold * sin(t * 0.00067 + ((uv.y * 1.4) + uv.x * 0.6) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.038 * (0.35 + sideLift) * (0.35 + noise) * sin(t * 0.00079 + ((uv.y * 4.6) + noise * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha = bucketAlpha(clamp(alpha, 0.025, 0.96));&#039;,&lt;br /&gt;
        &#039;  float value = alpha * dotMask;&#039;,&lt;br /&gt;
        &#039;  gl_FragColor = vec4(vec3(0.8862745 * value), 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function compileShader(type, source) {&lt;br /&gt;
        var shader = gl.createShader(type);&lt;br /&gt;
        gl.shaderSource(shader, source);&lt;br /&gt;
        gl.compileShader(shader);&lt;br /&gt;
&lt;br /&gt;
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {&lt;br /&gt;
            console.error(&#039;WebGL shader compile error:&#039;, gl.getShaderInfoLog(shader));&lt;br /&gt;
            gl.deleteShader(shader);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return shader;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexShader = compileShader(gl.VERTEX_SHADER, vertexSrc);&lt;br /&gt;
    var fragmentShader = compileShader(gl.FRAGMENT_SHADER, fragmentSrc);&lt;br /&gt;
&lt;br /&gt;
    if (!vertexShader || !fragmentShader) return;&lt;br /&gt;
&lt;br /&gt;
    var program = gl.createProgram();&lt;br /&gt;
    gl.attachShader(program, vertexShader);&lt;br /&gt;
    gl.attachShader(program, fragmentShader);&lt;br /&gt;
    gl.linkProgram(program);&lt;br /&gt;
&lt;br /&gt;
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {&lt;br /&gt;
        console.error(&#039;WebGL program link error:&#039;, gl.getProgramInfoLog(program));&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var positionLoc = gl.getAttribLocation(program, &#039;a_position&#039;);&lt;br /&gt;
    var resolutionLoc = gl.getUniformLocation(program, &#039;u_resolution&#039;);&lt;br /&gt;
    var timeLoc = gl.getUniformLocation(program, &#039;u_time&#039;);&lt;br /&gt;
&lt;br /&gt;
    var buffer = gl.createBuffer();&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([&lt;br /&gt;
        -1, -1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
         1,  1&lt;br /&gt;
    ]), gl.STATIC_DRAW);&lt;br /&gt;
&lt;br /&gt;
    gl.useProgram(program);&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.enableVertexAttribArray(positionLoc);&lt;br /&gt;
    gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
    function resize() {&lt;br /&gt;
        var dpr = Math.min(window.devicePixelRatio || 1, 1.5);&lt;br /&gt;
        var cssW = Math.max(1, window.innerWidth || document.documentElement.clientWidth || 1);&lt;br /&gt;
        var cssH = Math.max(1, window.innerHeight || document.documentElement.clientHeight || 1);&lt;br /&gt;
        var w = Math.max(1, Math.floor(cssW * dpr));&lt;br /&gt;
        var h = Math.max(1, Math.floor(cssH * dpr));&lt;br /&gt;
&lt;br /&gt;
        if (canvas.width !== w || canvas.height !== h) {&lt;br /&gt;
            canvas.width = w;&lt;br /&gt;
            canvas.height = h;&lt;br /&gt;
            canvas.style.width = cssW + &#039;px&#039;;&lt;br /&gt;
            canvas.style.height = cssH + &#039;px&#039;;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var prefersReducedMotion = false;&lt;br /&gt;
    try {&lt;br /&gt;
        prefersReducedMotion = window.matchMedia &amp;amp;&amp;amp; window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches;&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneNode() {&lt;br /&gt;
        return document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneState() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            return &#039;none&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) {&lt;br /&gt;
            return &#039;active&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundBusy === &#039;function&#039; &amp;amp;&amp;amp; instance.isHalftoneBackgroundBusy()) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;present&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function shouldSkipHalftoneDrawForGlobe() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) return false;&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Background pause/resume — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * While the nations globe is being held, keep the last rendered halftone&lt;br /&gt;
         * frame on screen and pause the halftone clock.  The important detail is&lt;br /&gt;
         * that this is not a wall-clock catch-up: skipped seconds are not replayed&lt;br /&gt;
         * or fast-forwarded when the pointer is released.&lt;br /&gt;
         */&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) return true;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; instance.dragging) return true;&lt;br /&gt;
&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundHardBusy === &#039;function&#039;) {&lt;br /&gt;
            return instance.isHalftoneBackgroundHardBusy();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) return true;&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFrameInterval() {&lt;br /&gt;
        if (prefersReducedMotion) {&lt;br /&gt;
            return 1000;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Nations background parity — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * The nations page used to permanently lower the halftone cadence while&lt;br /&gt;
         * the globe existed.  Profiling showed that the steady halftone draw is&lt;br /&gt;
         * normally cheap; the real contention happens during globe drag, first&lt;br /&gt;
         * WebGL texture upload, and long layout/render tasks.  Keep the same&lt;br /&gt;
         * full-quality cadence as ordinary pages, and skip only the frames that&lt;br /&gt;
         * would directly collide with an active/busy globe moment.&lt;br /&gt;
         */&lt;br /&gt;
        return 66;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var lastFrame = 0;&lt;br /&gt;
    var startTime = performance.now();&lt;br /&gt;
    var visualTime = 0;&lt;br /&gt;
    var lastVisualNow = 0;&lt;br /&gt;
&lt;br /&gt;
    function draw(now) {&lt;br /&gt;
        var state = getNationsGlobeHalftoneState();&lt;br /&gt;
        var interval = getFrameInterval();&lt;br /&gt;
        var perf = window.InteractionPerf;&lt;br /&gt;
        var delta;&lt;br /&gt;
&lt;br /&gt;
        if (!lastVisualNow) {&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
        }&lt;br /&gt;
        delta = Math.max(0, Math.min(120, now - lastVisualNow));&lt;br /&gt;
        lastVisualNow = now;&lt;br /&gt;
        visualTime += delta;&lt;br /&gt;
&lt;br /&gt;
        function runDraw() {&lt;br /&gt;
            resize();&lt;br /&gt;
&lt;br /&gt;
            gl.clearColor(0, 0, 0, 1);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.uniform2f(resolutionLoc, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform1f(timeLoc, visualTime);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLES, 0, 6);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (perf &amp;amp;&amp;amp; typeof perf.measureSync === &#039;function&#039;) {&lt;br /&gt;
            return perf.measureSync(&#039;background halftone draw&#039;, { globeState: state, interval: interval }, runDraw);&lt;br /&gt;
        }&lt;br /&gt;
        return runDraw();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(now) {&lt;br /&gt;
        if (halftoneState.runId !== runId || halftoneState.contextLost) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(render);&lt;br /&gt;
&lt;br /&gt;
        if (document.hidden) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (now - lastFrame &amp;lt; getFrameInterval()) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (shouldSkipHalftoneDrawForGlobe()) {&lt;br /&gt;
            lastFrame = now;&lt;br /&gt;
            halftoneState.lastGlobeBusySkipAt = now;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Pause the halftone clock while a hard-busy globe frame owns the&lt;br /&gt;
             * budget.  Without this, the next visible background frame catches&lt;br /&gt;
             * up to wall-clock time and the pattern appears to skip forward.&lt;br /&gt;
             * Updating lastVisualNow here makes the animation resume from the&lt;br /&gt;
             * last visible moment instead of replaying the hidden gap.&lt;br /&gt;
             */&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        draw(now);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    draw(performance.now());&lt;br /&gt;
    requestAnimationFrame(render);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SVG_BELL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_BELL_DOT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell-dot&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M11.68 2.009A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673c-.824-.85-1.678-1.731-2.21-3.348&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;18&amp;quot; cy=&amp;quot;5&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LIST = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-list&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 5h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 12h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 19h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 5h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 12h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 19h13&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LANGUAGES = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-languages&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;m5 8 6 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m4 14 6-6 2-3&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M2 5h12&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 2h1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m22 22-5-10-5 10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 18h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_POWER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-power&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 2v10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.4 6.6a9 9 0 1 1-12.77.04&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SETTINGS = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-settings&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_TEXT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-text&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 8h8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 12h10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 16h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_EYE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-eye&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_NEWSPAPER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-newspaper&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M15 18h-5&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 14h-8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-4 0v-9a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;8&amp;quot; height=&amp;quot;4&amp;quot; x=&amp;quot;10&amp;quot; y=&amp;quot;6&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_GREAT_WALL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-great-wall lucide lucide-paint-roller&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;rect width=&amp;quot;16&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;2&amp;quot; y=&amp;quot;2&amp;quot; rx=&amp;quot;2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M10 16v-2a2 2 0 0 1 2-2h8a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;4&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;8&amp;quot; y=&amp;quot;16&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_TROPHY = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-trophy&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 9h1.5a1 1 0 0 0 0-5H18&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9H4.5a1 1 0 0 1 0-5H6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_PACKAGE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-package&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 3v6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M16.76 3a2 2 0 0 1 1.8 1.1l2.23 4.479a2 2 0 0 1 .21.891V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9.472a2 2 0 0 1 .211-.894L5.45 4.1A2 2 0 0 1 7.24 3z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.054 9.013h17.893&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
var PROFILE_RENDER_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function invalidateProfileRender() {&lt;br /&gt;
    PROFILE_RENDER_TOKEN++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    initHalftoneBackground();&lt;br /&gt;
&lt;br /&gt;
// ── 하단 Plank 단축키 가이드 ──&lt;br /&gt;
function escapeClbiBottomGuideHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiBottomGuidePageName() {&lt;br /&gt;
    var pageName = window.mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) {&lt;br /&gt;
        pageName = window.location.pathname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return String(pageName)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsShortcutContext() {&lt;br /&gt;
    var pageName = readClbiBottomGuidePageName();&lt;br /&gt;
&lt;br /&gt;
    return pageName === &#039;시대&#039; ||&lt;br /&gt;
        pageName === &#039;Era&#039; ||&lt;br /&gt;
        !!document.querySelector(&#039;.clbi-nations-panel-stack, .clbi-nations-globe-window, .clbi-nations-tabpanel&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiWikiEditShortcutContext() {&lt;br /&gt;
    var action = window.mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgAction&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    var search = String(window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
    return action === &#039;edit&#039; ||&lt;br /&gt;
        action === &#039;submit&#039; ||&lt;br /&gt;
        /(?:^|[?&amp;amp;])action=(?:edit|submit)(?:&amp;amp;|$)/.test(search) ||&lt;br /&gt;
        !!document.querySelector(&#039;#editform, #wpSave, input[name=&amp;quot;wpSave&amp;quot;], button[name=&amp;quot;wpSave&amp;quot;]&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function isClbiNationListManagerShortcutContext() {&lt;br /&gt;
    return !!document.querySelector(&#039;.nation-list-manager&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiBottomShortcutItems() {&lt;br /&gt;
    if (isClbiWikiEditShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;변경사항 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationListManagerShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;국가 목록 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Q&#039;, label: &#039;이전 시대&#039; },&lt;br /&gt;
            { key: &#039;E&#039;, label: &#039;다음 시대&#039; },&lt;br /&gt;
            { key: &#039;Shift+Q&#039;, label: &#039;이전 연도&#039; },&lt;br /&gt;
            { key: &#039;Shift+E&#039;, label: &#039;다음 연도&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+Q&#039;, label: &#039;이전 대륙&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+E&#039;, label: &#039;다음 대륙&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return [];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderClbiBottomShortcutGuide() {&lt;br /&gt;
    var guide = document.getElementById(&#039;clbi-bottom-shortcut-guide&#039;);&lt;br /&gt;
    var items;&lt;br /&gt;
    var html;&lt;br /&gt;
&lt;br /&gt;
    if (!guide) return;&lt;br /&gt;
&lt;br /&gt;
    items = getClbiBottomShortcutItems();&lt;br /&gt;
&lt;br /&gt;
    if (!items.length) {&lt;br /&gt;
        guide.classList.add(&#039;is-empty&#039;);&lt;br /&gt;
        guide.innerHTML = &#039;&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    guide.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    html = &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-list&amp;quot;&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        html += &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-key&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.key) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-label&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    html += &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    guide.innerHTML = html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiBottomPlankHtml(wrapId, navId, mainId) {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-bottom-shortcut-guide&amp;quot; class=&amp;quot;is-empty&amp;quot; aria-label=&amp;quot;단축키 안내&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
&lt;br /&gt;
function getClbiNationsTabAtPointer(tabpanel) {&lt;br /&gt;
    var element;&lt;br /&gt;
    var tab;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return null;&lt;br /&gt;
    if (CLBI_NATIONS_LAST_POINTER_X === null || CLBI_NATIONS_LAST_POINTER_Y === null) return null;&lt;br /&gt;
    if (typeof document.elementFromPoint !== &#039;function&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
    element = document.elementFromPoint(CLBI_NATIONS_LAST_POINTER_X, CLBI_NATIONS_LAST_POINTER_Y);&lt;br /&gt;
    if (!element) return null;&lt;br /&gt;
&lt;br /&gt;
    tab = element.closest ? element.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (!tab || !tabpanel.contains(tab)) return null;&lt;br /&gt;
&lt;br /&gt;
    return tab;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateClbiNationsPointerHover(tabpanel, forceSuppress) {&lt;br /&gt;
    var tab = getClbiNationsTabAtPointer(tabpanel);&lt;br /&gt;
    var suppressContinent;&lt;br /&gt;
    var tabContinent;&lt;br /&gt;
    var tabIsActive;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
&lt;br /&gt;
    if (!tab) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabContinent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    tabIsActive = tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (tabIsActive) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    suppressContinent = tabpanel.getAttribute(&#039;data-clbi-hover-suppress-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (forceSuppress || !suppressContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-hover-suppress-continent&#039;, tabContinent);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (suppressContinent === tabContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * The pointer actually moved onto another tab after the keyboard switch.&lt;br /&gt;
     * At that point this is no longer stale browser :hover; let normal hover work.&lt;br /&gt;
     */&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateAllClbiNationsPointerHovers() {&lt;br /&gt;
    var panels = document.querySelectorAll(&#039;.clbi-nations-tabpanel.is-keyboard-switching&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        if (isClbiNationsPanelOwnedTabpanel(tabpanel)) return;&lt;br /&gt;
        validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsPanelOwnedTabpanel(tabpanel) {&lt;br /&gt;
    /*&lt;br /&gt;
     * Mouse continent tab regression guard.&lt;br /&gt;
     * -------------------------------------&lt;br /&gt;
     * Detached SPA preparation serializes attributes/classes but it cannot&lt;br /&gt;
     * serialize DOM event listeners or JS properties.  The NationsPanel fix&lt;br /&gt;
     * therefore uses the DOM property CLBI_NationsPanelOwned /&lt;br /&gt;
     * CLBI_NationsTabPanelBound as the real ownership marker.&lt;br /&gt;
     *&lt;br /&gt;
     * Do NOT treat data-nations-tabpanel-ready or clbi-nations-continent-cache&lt;br /&gt;
     * as ownership here.  Those can survive innerHTML insertion while the real&lt;br /&gt;
     * click listeners were lost, causing Common.js to delegate to a panel that&lt;br /&gt;
     * NationsPanel has not rebound yet.  The fallback below must remain able to&lt;br /&gt;
     * handle mouse clicks until NationsPanel reclaims the live DOM node.&lt;br /&gt;
     */&lt;br /&gt;
    return !!(tabpanel &amp;amp;&amp;amp; tabpanel.CLBI_NationsPanelOwned);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsLiveContinentActive(tabpanel, continent) {&lt;br /&gt;
    var tab;&lt;br /&gt;
    var panel;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !continent) return false;&lt;br /&gt;
&lt;br /&gt;
    tab = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panel = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent-panel&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return !!(&lt;br /&gt;
        tab &amp;amp;&amp;amp;&lt;br /&gt;
        panel &amp;amp;&amp;amp;&lt;br /&gt;
        (tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.classList.contains(&#039;is-active&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.getAttribute(&#039;aria-hidden&#039;) !== &#039;true&#039; &amp;amp;&amp;amp;&lt;br /&gt;
        !panel.hasAttribute(&#039;hidden&#039;)&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function activateClbiNationsContinent(tabpanel, targetContinent, options) {&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var panels;&lt;br /&gt;
    var skipOwner = !!(options &amp;amp;&amp;amp; options.skipOwner);&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !targetContinent) return false;&lt;br /&gt;
&lt;br /&gt;
    if (!skipOwner &amp;amp;&amp;amp; isClbiNationsPanelOwnedTabpanel(tabpanel)) {&lt;br /&gt;
        var owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
&lt;br /&gt;
        if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (owner.activateContinent(targetContinent, {&lt;br /&gt;
                    source: &#039;common-legacy-click&#039;,&lt;br /&gt;
                    panel: tabpanel&lt;br /&gt;
                })) {&lt;br /&gt;
                    return true;&lt;br /&gt;
                }&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Ownership markers can be stale during SPA/hydration edge cases.  If&lt;br /&gt;
         * the owner API is missing or refuses the live panel, do not drop the&lt;br /&gt;
         * mouse click.  Fall through to the local fallback so pointer users are&lt;br /&gt;
         * never left with keyboard-only continent tabs.&lt;br /&gt;
         */&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    panels = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
    if (!tabs.length || !panels.length) return false;&lt;br /&gt;
&lt;br /&gt;
    tabs.forEach(function (tab) {&lt;br /&gt;
        var active = tab.getAttribute(&#039;data-continent&#039;) === targetContinent;&lt;br /&gt;
        tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
        tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panels.forEach(function (panel) {&lt;br /&gt;
        var active = panel.getAttribute(&#039;data-continent-panel&#039;) === targetContinent;&lt;br /&gt;
        panel.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
        if (active) {&lt;br /&gt;
            panel.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            panel.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.sync === &#039;function&#039;) window.CLBI_DECORATIONS.sync();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    validateClbiNationsPointerHover(tabpanel, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
    tabpanel.classList.remove(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
    tabpanel.removeAttribute(&#039;data-clbi-hover-suppress-continent&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveClbiNationsContinent(direction) {&lt;br /&gt;
    var tabpanel = document.querySelector(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var activeIndex;&lt;br /&gt;
    var nextIndex;&lt;br /&gt;
    var target;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return false;&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsPanelOwnedTabpanel(tabpanel) &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI_NATIONS_PANEL &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI_NATIONS_PANEL.moveContinent === &#039;function&#039;) {&lt;br /&gt;
        return !!window.CLBI_NATIONS_PANEL.moveContinent(direction, {&lt;br /&gt;
            source: &#039;common-legacy-keyboard&#039;,&lt;br /&gt;
            panel: tabpanel&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
    activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
        return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
    nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
    target = tabs[nextIndex].getAttribute(&#039;data-continent&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (activateClbiNationsContinent(tabpanel, target)) {&lt;br /&gt;
        setClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
        }, 0);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsTabpanelControls(root) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var panels = scope.querySelectorAll(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        /*&lt;br /&gt;
         * Common.js is only a safety fallback for continent tabs; NationsPanel&lt;br /&gt;
         * is the primary owner.  This guard must be a DOM property, not a&lt;br /&gt;
         * serialized attribute.  SPA warm prepaint can preserve&lt;br /&gt;
         * data-clbi-nations-tabs-ready=&amp;quot;1&amp;quot; while losing the actual event&lt;br /&gt;
         * listeners, which was the root cause of mouse clicks no longer moving&lt;br /&gt;
         * continents while keyboard shortcuts still worked.&lt;br /&gt;
         */&lt;br /&gt;
        if (tabpanel.CLBI_NationsCommonTabsBound) return;&lt;br /&gt;
        tabpanel.CLBI_NationsCommonTabsBound = true;&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-nations-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerdown&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
            if (activateClbiNationsContinent(tabpanel, tab.getAttribute(&#039;data-continent&#039;))) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
            var handled = false;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsContinent(-1);&lt;br /&gt;
            else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsContinent(1);&lt;br /&gt;
&lt;br /&gt;
            if (handled) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                event.stopPropagation();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function handleClbiNationsContinentDocumentClick(event) {&lt;br /&gt;
    var target;&lt;br /&gt;
    var tab;&lt;br /&gt;
    var tabpanel;&lt;br /&gt;
    var continent;&lt;br /&gt;
    var owner;&lt;br /&gt;
    var handled = false;&lt;br /&gt;
&lt;br /&gt;
    if (!event || event.__clbiNationsContinentHandled) return;&lt;br /&gt;
    target = event.target;&lt;br /&gt;
    if (!target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
    tab = target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;);&lt;br /&gt;
    if (!tab) return;&lt;br /&gt;
&lt;br /&gt;
    tabpanel = tab.closest ? tab.closest(&#039;.clbi-nations-tabpanel&#039;) : null;&lt;br /&gt;
    if (!tabpanel || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
    continent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    if (!continent) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Final live-DOM mouse delegate.&lt;br /&gt;
     * --------------------------------&lt;br /&gt;
     * The continent tab UI has two initialization phases: detached preparation&lt;br /&gt;
     * for seamless SPA entry, then live DOM hydration.  Detached preparation can&lt;br /&gt;
     * leave serialized &amp;quot;ready&amp;quot; attributes behind while losing event listeners.&lt;br /&gt;
     * Keyboard shortcuts still work because they call the public NationsPanel&lt;br /&gt;
     * API directly, but mouse clicks depend on live listeners.  This capturing&lt;br /&gt;
     * delegate is intentionally independent of per-panel ready attributes: it&lt;br /&gt;
     * always resolves the clicked live tab and hands it to NationsPanel first,&lt;br /&gt;
     * then falls back to the local Common.js switcher if needed.&lt;br /&gt;
     */&lt;br /&gt;
    event.__clbiNationsContinentHandled = true;&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
    owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
    if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
        try {&lt;br /&gt;
            handled = !!owner.activateContinent(continent, {&lt;br /&gt;
                source: &#039;mouse-click-live-delegate&#039;,&lt;br /&gt;
                panel: tabpanel&lt;br /&gt;
            });&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Owner API stale-cache guard.&lt;br /&gt;
         *&lt;br /&gt;
         * A previous regression came from NationsPanel returning true after it&lt;br /&gt;
         * updated cached/detached tab nodes instead of the clicked live tab. A&lt;br /&gt;
         * true return value alone is therefore not enough for mouse input. Verify&lt;br /&gt;
         * the live DOM that received the click. If it did not become active,&lt;br /&gt;
         * bypass the owner and run the Common.js live query fallback directly.&lt;br /&gt;
         */&lt;br /&gt;
        if (handled &amp;amp;&amp;amp; !isClbiNationsLiveContinentActive(tabpanel, continent)) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!handled) {&lt;br /&gt;
        handled = activateClbiNationsContinent(tabpanel, continent, { skipOwner: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (handled) {&lt;br /&gt;
        try { playStaticSound(); } catch (err2) {}&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) event.stopImmediatePropagation();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsGlobalClickDelegate() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-global-click-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-global-click-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleClbiNationsContinentDocumentClick, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsPointerHoverValidation() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointermove&#039;, function (event) {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = event.clientX;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = event.clientY;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiBottomShortcutSystem(root) {&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsGlobalClickDelegate();&lt;br /&gt;
    initClbiNationsTabpanelControls(root || document);&lt;br /&gt;
    initClbiNationsPointerHoverValidation();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// ── 상·하단 네비게이션 바 ──&lt;br /&gt;
function buildClbiNavHtml(position) {&lt;br /&gt;
    var isBottom = position === &#039;bottom&#039;;&lt;br /&gt;
    var base = isBottom ? &#039;clbi-bottom&#039; : &#039;clbi-top&#039;;&lt;br /&gt;
    var shortBase = isBottom ? &#039;clbi-bnav&#039; : &#039;clbi-tnav&#039;;&lt;br /&gt;
    var wrapId = base + &#039;-nav-wrap&#039;;&lt;br /&gt;
    var navId = base + &#039;-nav&#039;;&lt;br /&gt;
    var mainId = base + &#039;-nav-main&#039;;&lt;br /&gt;
    var tabsId = base + &#039;-nav-tabs&#039;;&lt;br /&gt;
    var searchId = base + &#039;-nav-search&#039;;&lt;br /&gt;
    var inputId = isBottom ? &#039;clbi-bottom-search-input&#039; : &#039;clbi-top-search-input&#039;;&lt;br /&gt;
    var worldId = shortBase + &#039;-worldbuilding&#039;;&lt;br /&gt;
    var subId = isBottom ? &#039;clbi-bottom-sub-worldbuilding&#039; : &#039;clbi-sub-worldbuilding&#039;;&lt;br /&gt;
    var subInnerId = subId + &#039;-inner&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (isBottom) {&lt;br /&gt;
        return buildClbiBottomPlankHtml(wrapId, navId, mainId);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + tabsId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/대문&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-main-menu-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;메인 메뉴&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-project-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;프로젝트&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-top-nav-item&amp;quot; id=&amp;quot;&#039; + worldId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-worldbuilding-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;세계관&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-arrow&amp;quot;&amp;gt;▾&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    (isBottom ? &#039;&#039; : (&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;&#039; + searchId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;&#039; + inputId + &#039;&amp;quot; placeholder=&amp;quot;검색...&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                    )) +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + subId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + subInnerId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-tnav-sub-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/시대&amp;quot;&amp;gt;시대&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/설정&amp;quot;&amp;gt;설정&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/기업_및_공동체&amp;quot;&amp;gt;기업 및 공동체&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/군_정치집단&amp;quot;&amp;gt;군, 정치집단&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/인물&amp;quot;&amp;gt;인물&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeClbiShellDomOrder() {&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var canvas = document.getElementById(&#039;site-halftone-bg&#039;);&lt;br /&gt;
    var anchor;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var topRect;&lt;br /&gt;
    var wrapperRect;&lt;br /&gt;
    var needsRecovery;&lt;br /&gt;
    var expectedWrapperTop;&lt;br /&gt;
    var host;&lt;br /&gt;
&lt;br /&gt;
    if (!contentWrapper || !topNav || !bottomNav || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    CLBI shell can live inside a Liberty &amp;lt;section&amp;gt;.  Some skins/layouts give that&lt;br /&gt;
    section a flow context that lets the top nav visually overlap the content&lt;br /&gt;
    wrapper even when DOM sibling order is correct.  Mark the common parent and&lt;br /&gt;
    let Layout.css force a simple vertical flow for the shell.&lt;br /&gt;
    */&lt;br /&gt;
    host = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp;&lt;br /&gt;
        contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? contentWrapper.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (host) {&lt;br /&gt;
        host.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    viewportH = window.innerHeight || document.documentElement.clientHeight || 0;&lt;br /&gt;
    topRect = topNav.getBoundingClientRect();&lt;br /&gt;
    wrapperRect = contentWrapper.getBoundingClientRect();&lt;br /&gt;
    expectedWrapperTop = topRect.bottom + 8;&lt;br /&gt;
&lt;br /&gt;
    needsRecovery = false;&lt;br /&gt;
&lt;br /&gt;
    if (viewportH &amp;gt; 0) {&lt;br /&gt;
        if (topRect.top &amp;gt;= viewportH * 0.55) needsRecovery = true;&lt;br /&gt;
        if (wrapperRect.top &amp;gt;= viewportH * 0.60) needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (topRect.top &amp;gt; 240 || wrapperRect.top &amp;gt; 320) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (wrapperRect.top &amp;lt; expectedWrapperTop - 1) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!needsRecovery) {&lt;br /&gt;
        document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    anchor = canvas &amp;amp;&amp;amp; canvas.parentNode === document.body&lt;br /&gt;
        ? canvas.nextSibling&lt;br /&gt;
        : document.body.firstChild;&lt;br /&gt;
&lt;br /&gt;
    document.body.insertBefore(topNav, anchor);&lt;br /&gt;
    document.body.insertBefore(contentWrapper, topNav.nextSibling);&lt;br /&gt;
    document.body.insertBefore(bottomNav, contentWrapper.nextSibling);&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.normalizeClbiShellDomOrder = normalizeClbiShellDomOrder;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureClbiVerticalScaleHost() {&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var host;&lt;br /&gt;
    var existing;&lt;br /&gt;
    var parent;&lt;br /&gt;
&lt;br /&gt;
    if (!topNav || !contentWrapper || !bottomNav || !document.body) return null;&lt;br /&gt;
&lt;br /&gt;
    existing = document.getElementById(&#039;clbi-shell-scale-host&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (existing &amp;amp;&amp;amp; existing.contains(topNav) &amp;amp;&amp;amp; existing.contains(contentWrapper) &amp;amp;&amp;amp; existing.contains(bottomNav)) {&lt;br /&gt;
        existing.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        return existing;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    parent = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp; contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? topNav.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (parent &amp;amp;&amp;amp; parent !== document.body) {&lt;br /&gt;
        parent.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        parent.id = parent.id || &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
        return parent;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host = existing || document.createElement(&#039;div&#039;);&lt;br /&gt;
    host.id = &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
    host.className = &#039;clbi-shell-host&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!host.parentNode) {&lt;br /&gt;
        document.body.insertBefore(host, topNav);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host.appendChild(topNav);&lt;br /&gt;
    host.appendChild(contentWrapper);&lt;br /&gt;
    host.appendChild(bottomNav);&lt;br /&gt;
&lt;br /&gt;
    return host;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiRootPx(name, fallback) {&lt;br /&gt;
    var raw = &#039;&#039;;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        raw = getComputedStyle(document.documentElement).getPropertyValue(name);&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    value = parseFloat(raw);&lt;br /&gt;
    return isFinite(value) &amp;amp;&amp;amp; value &amp;gt; 0 ? value : fallback;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiShellVerticalScale() {&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    var host;&lt;br /&gt;
    var topInner;&lt;br /&gt;
    var bottomInner;&lt;br /&gt;
    var thresholdH;&lt;br /&gt;
    var gap;&lt;br /&gt;
    var outerGapTotal;&lt;br /&gt;
    var baseStageH;&lt;br /&gt;
    var stageW;&lt;br /&gt;
    var stageH;&lt;br /&gt;
    var viewportW;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var availableW;&lt;br /&gt;
    var availableH;&lt;br /&gt;
    var topH;&lt;br /&gt;
    var bottomH;&lt;br /&gt;
    var contentH;&lt;br /&gt;
    var widthScale;&lt;br /&gt;
    var heightScale;&lt;br /&gt;
    var scale;&lt;br /&gt;
    var shouldScale;&lt;br /&gt;
&lt;br /&gt;
    if (!root || !body) return;&lt;br /&gt;
&lt;br /&gt;
    host = ensureClbiVerticalScaleHost();&lt;br /&gt;
    if (!host) return;&lt;br /&gt;
&lt;br /&gt;
    thresholdH = readClbiRootPx(&#039;--clbi-vertical-scale-threshold-h&#039;, 1080);&lt;br /&gt;
    gap = readClbiRootPx(&#039;--layout-gap&#039;, 8);&lt;br /&gt;
    outerGapTotal = gap * 2;&lt;br /&gt;
    baseStageH = Math.max(420, thresholdH - outerGapTotal);&lt;br /&gt;
    stageW = readClbiRootPx(&#039;--layout-shell-w&#039;, 1880);&lt;br /&gt;
&lt;br /&gt;
    viewportW = Math.max(320, window.innerWidth || document.documentElement.clientWidth || stageW);&lt;br /&gt;
    viewportH = Math.max(320, window.innerHeight || document.documentElement.clientHeight || thresholdH);&lt;br /&gt;
    availableW = Math.max(240, viewportW - outerGapTotal);&lt;br /&gt;
    availableH = Math.max(240, viewportH - outerGapTotal);&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    평상시에는 기존 세로 채움 레이아웃을 기준으로 삼는다.&lt;br /&gt;
    세로가 기준점보다 작아지는 경우에는 기준 높이를 고정점으로 삼고,&lt;br /&gt;
    가로만 부족한 경우에는 현재 사용 가능한 세로 높이를 고정점으로 삼는다.&lt;br /&gt;
    이렇게 해야 가로 부족으로 scale에 들어갈 때 본문 높이가 갑자기 접히지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    stageH = availableH &amp;gt;= baseStageH ? availableH : baseStageH;&lt;br /&gt;
&lt;br /&gt;
    topInner = document.getElementById(&#039;clbi-top-nav&#039;);&lt;br /&gt;
    bottomInner = document.getElementById(&#039;clbi-bottom-nav&#039;);&lt;br /&gt;
&lt;br /&gt;
    topH = topInner ? Math.ceil(topInner.offsetHeight || topInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    bottomH = bottomInner ? Math.ceil(bottomInner.offsetHeight || bottomInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    contentH = Math.max(360, Math.floor(stageH - topH - bottomH - (gap * 2)));&lt;br /&gt;
&lt;br /&gt;
    widthScale = availableW &amp;lt; stageW ? availableW / stageW : 1;&lt;br /&gt;
    heightScale = availableH &amp;lt; baseStageH ? availableH / baseStageH : 1;&lt;br /&gt;
    scale = Math.min(1, widthScale, heightScale);&lt;br /&gt;
    scale = Math.max(0.50, Math.min(1, Math.floor(scale * 1000) / 1000));&lt;br /&gt;
    shouldScale = scale &amp;lt; 0.999;&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-w&#039;, stageW + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-h&#039;, Math.floor(stageH) + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-content-h&#039;, contentH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-shell-scale&#039;, String(scale));&lt;br /&gt;
&lt;br /&gt;
    body.classList.toggle(&#039;clbi-shell-vertical-scale&#039;, shouldScale);&lt;br /&gt;
&lt;br /&gt;
    if (shouldScale) {&lt;br /&gt;
        resetLeftRecentAdaptiveState();&lt;br /&gt;
        resetLeftBillboardAdaptiveState();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
window.updateClbiShellVerticalScale = updateClbiShellVerticalScale;&lt;br /&gt;
&lt;br /&gt;
var $contentWrapper = $(&#039;.content-wrapper&#039;).first();&lt;br /&gt;
&lt;br /&gt;
if ($contentWrapper.length) {&lt;br /&gt;
    $(&#039;#clbi-top-nav-wrap, #clbi-bottom-nav-wrap&#039;).remove();&lt;br /&gt;
    $contentWrapper.before(buildClbiNavHtml(&#039;top&#039;));&lt;br /&gt;
    $contentWrapper.after(buildClbiNavHtml(&#039;bottom&#039;));&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsTabpanelControls(document);&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
function runClbiShellMetricsBatch() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var topH = 0;&lt;br /&gt;
    var bottomH = 0;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    if (top) {&lt;br /&gt;
        topH = Math.ceil(top.getBoundingClientRect().height || top.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (bottom) {&lt;br /&gt;
        bottomH = Math.ceil(bottom.getBoundingClientRect().height || bottom.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-top-nav-outer-h&#039;, topH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-bottom-nav-outer-h&#039;, bottomH + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (typeof updateClbiShellVerticalScale === &#039;function&#039;) {&lt;br /&gt;
        updateClbiShellVerticalScale();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestClbiShellMetricsFrame() {&lt;br /&gt;
    if (CLBI_SHELL_METRICS_RAF !== null) return;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = window.requestAnimationFrame&lt;br /&gt;
        ? window.requestAnimationFrame(runClbiShellMetricsBatch)&lt;br /&gt;
        : window.setTimeout(runClbiShellMetricsBatch, 16);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiShellMetrics() {&lt;br /&gt;
    requestClbiShellMetricsFrame();&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 0);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 80);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 240);&lt;br /&gt;
}&lt;br /&gt;
function watchClbiShellMetrics() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var observer;&lt;br /&gt;
&lt;br /&gt;
    scheduleClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
    $(window).on(&#039;resize orientationchange&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    $(window).on(&#039;pageshow.clbiShellScale focus.clbiShellScale&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
        if (!document.hidden) scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
    $(window).on(&#039;resize.clbiLeftBillboard orientationchange.clbiLeftBillboard&#039;, scheduleLeftSidebarVerticalFit);&lt;br /&gt;
    $(window).on(&#039;resize.clbiRecentViewport orientationchange.clbiRecentViewport&#039;, function () { scheduleAdaptiveLeftRecentItems(); scheduleClbiContentBottomGap(); });&lt;br /&gt;
    $(window).on(&#039;resize.clbiContentBottomGap orientationchange.clbiContentBottomGap&#039;, scheduleClbiContentBottomGap);&lt;br /&gt;
&lt;br /&gt;
    if (window.ResizeObserver) {&lt;br /&gt;
        observer = new ResizeObserver(scheduleClbiShellMetrics);&lt;br /&gt;
        if (top) observer.observe(top);&lt;br /&gt;
        if (bottom) observer.observe(bottom);&lt;br /&gt;
        window.CLBI_SHELL_RESIZE_OBSERVER = observer;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindClbiWorldbuildingToggle(buttonSelector, menuSelector) {&lt;br /&gt;
    $(buttonSelector).on(&#039;click&#039;, function() {&lt;br /&gt;
        var $menu = $(menuSelector);&lt;br /&gt;
        var $btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        $menu.toggleClass(&#039;worldbuilding-open&#039;);&lt;br /&gt;
        $btn.toggleClass(&#039;clbi-tnav-active&#039;, $menu.hasClass(&#039;worldbuilding-open&#039;));&lt;br /&gt;
        scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-tnav-worldbuilding&#039;, &#039;#clbi-sub-worldbuilding&#039;);&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-bnav-worldbuilding&#039;, &#039;#clbi-bottom-sub-worldbuilding&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-top-search-input, #clbi-bottom-search-input&#039;).on(&#039;keydown&#039;, function(e) {&lt;br /&gt;
    if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
        var q = $(this).val().trim();&lt;br /&gt;
        if (q) window.location.href = &#039;/index.php?search=&#039; + encodeURIComponent(q);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
        initClbiBottomShortcutSystem($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
watchClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// 페이지 전환 사운드&lt;br /&gt;
var transitionSound = new Audio(&#039;/index.php?title=특수:Redirect/file/Sfx-ui-001.mp3&#039;);&lt;br /&gt;
&lt;br /&gt;
(function() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
    transitionSound.volume = sfxOn ? master * sfx : 0;&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
function playStaticSound() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!sfxOn) return;&lt;br /&gt;
&lt;br /&gt;
    transitionSound.volume = master * sfx;&lt;br /&gt;
    transitionSound.currentTime = 0;&lt;br /&gt;
    transitionSound.play();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 현재 언어 감지&lt;br /&gt;
function getCurrentLang() {&lt;br /&gt;
    var langData = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    return langData ? (langData.getAttribute(&#039;data-lang&#039;) || &#039;ko&#039;) : &#039;ko&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizePageName(value) {&lt;br /&gt;
    return String(value || &#039;&#039;)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildWikiPath(title) {&lt;br /&gt;
    return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLangShortCode(lang) {&lt;br /&gt;
    var map = { ko: &#039;KR&#039;, en: &#039;EN&#039;, zh: &#039;ZH&#039;, ja: &#039;JA&#039;, ru: &#039;RU&#039;, es: &#039;ES&#039; };&lt;br /&gt;
    return map[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLanguageTargetTitle(lang) {&lt;br /&gt;
    var data = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    if (!data || !lang) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var keys = [&lt;br /&gt;
        &#039;data-&#039; + lang,&lt;br /&gt;
        &#039;data-page-&#039; + lang,&lt;br /&gt;
        &#039;data-title-&#039; + lang,&lt;br /&gt;
        &#039;data-target-&#039; + lang,&lt;br /&gt;
        &#039;data-lang-&#039; + lang&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var value = data.getAttribute(keys[i]);&lt;br /&gt;
        if (value) return value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeClbiHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var SIDEBAR_LANG_SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_TITLE = &#039;MediaWiki:LanguageStatus.json&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;English&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;Русский&#039;,&lt;br /&gt;
    es: &#039;Español&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_DIAL_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;ENG&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;РУС&#039;,&lt;br /&gt;
    es: &#039;ESP&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_VALUES = {&lt;br /&gt;
    available: true,&lt;br /&gt;
    wip: true,&lt;br /&gt;
    unavailable: true&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageStatusRegistry = {};&lt;br /&gt;
var sidebarLanguageStatusLoaded = false;&lt;br /&gt;
var sidebarLanguageStatusLoading = false;&lt;br /&gt;
var sidebarLanguageStatusCallbacks = [];&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageState = {&lt;br /&gt;
    order: [&#039;ko&#039;, &#039;en&#039;, &#039;zh&#039;, &#039;ja&#039;, &#039;ru&#039;, &#039;es&#039;],&lt;br /&gt;
    currentLang: &#039;ko&#039;,&lt;br /&gt;
    baseIndex: 0,&lt;br /&gt;
    selectedIndex: 0,&lt;br /&gt;
    rotation: 0,&lt;br /&gt;
    dragging: false,&lt;br /&gt;
    dragMoved: false,&lt;br /&gt;
    dragStartX: 0,&lt;br /&gt;
    dragStartY: 0,&lt;br /&gt;
    dragStartRotation: 0,&lt;br /&gt;
    dragAxis: null,&lt;br /&gt;
    pointerCaptured: false,&lt;br /&gt;
    lastX: 0,&lt;br /&gt;
    lastTime: 0,&lt;br /&gt;
    releaseVelocity: 0,&lt;br /&gt;
    suppressClickUntil: 0,&lt;br /&gt;
    raf: null,&lt;br /&gt;
    pendingRotation: 0,&lt;br /&gt;
    snapTimer: null,&lt;br /&gt;
    inertiaRaf: null,&lt;br /&gt;
    navigateTimer: null,&lt;br /&gt;
    bound: false,&lt;br /&gt;
    boundElement: null,&lt;br /&gt;
    rotor: null,&lt;br /&gt;
    cx: 101,&lt;br /&gt;
    cy: 119,&lt;br /&gt;
    outerR: 109,&lt;br /&gt;
    innerR: 28,&lt;br /&gt;
    sectorAngle: 30,&lt;br /&gt;
    halfSector: 15,&lt;br /&gt;
    repeats: 8,&lt;br /&gt;
    dragSensitivity: 0.58,&lt;br /&gt;
    maxSpinVelocity: 1.75,&lt;br /&gt;
    minSpinVelocity: 0.055,&lt;br /&gt;
    spinDecel: 0.00185&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function createSidebarLanguageSvgEl(tag) {&lt;br /&gt;
    return document.createElementNS(SIDEBAR_LANG_SVG_NS, tag);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageIndex(index) {&lt;br /&gt;
    var length = sidebarLanguageState.order.length;&lt;br /&gt;
    var normalized = index % length;&lt;br /&gt;
    return normalized &amp;lt; 0 ? normalized + length : normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageDialName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_DIAL_LABELS[lang] || getSidebarLanguageName(lang);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageStatusValue(value) {&lt;br /&gt;
    value = String(value == null ? &#039;&#039; : value).toLowerCase().trim();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_STATUS_VALUES[value] ? value : &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusPageKey() {&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
&lt;br /&gt;
    return normalized || raw || &#039;대문&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusEntry() {&lt;br /&gt;
    var registry = sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    var pages = registry.pages &amp;amp;&amp;amp; typeof registry.pages === &#039;object&#039; ? registry.pages : registry;&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
    var title = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var keys = [&lt;br /&gt;
        normalized,&lt;br /&gt;
        raw,&lt;br /&gt;
        raw.replace(/_/g, &#039; &#039;),&lt;br /&gt;
        normalized.replace(/ /g, &#039;_&#039;),&lt;br /&gt;
        title,&lt;br /&gt;
        title.replace(/_/g, &#039; &#039;)&lt;br /&gt;
    ];&lt;br /&gt;
    var i;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; keys.length; i += 1) {&lt;br /&gt;
        if (keys[i] &amp;amp;&amp;amp; pages[keys[i]] &amp;amp;&amp;amp; typeof pages[keys[i]] === &#039;object&#039;) {&lt;br /&gt;
            return pages[keys[i]];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {};&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusOverride(lang) {&lt;br /&gt;
    var entry = getSidebarLanguageStatusEntry();&lt;br /&gt;
    return normalizeSidebarLanguageStatusValue(entry[lang]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function flushSidebarLanguageStatusCallbacks() {&lt;br /&gt;
    var callbacks = sidebarLanguageStatusCallbacks.slice();&lt;br /&gt;
    sidebarLanguageStatusCallbacks.length = 0;&lt;br /&gt;
&lt;br /&gt;
    callbacks.forEach(function(callback) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
            callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadSidebarLanguageStatusRegistry(callback, force) {&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        sidebarLanguageStatusCallbacks.push(callback);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoaded &amp;amp;&amp;amp; !force) {&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoading) return;&lt;br /&gt;
&lt;br /&gt;
    sidebarLanguageStatusLoading = true;&lt;br /&gt;
&lt;br /&gt;
    function finishLanguageStatus(parsed) {&lt;br /&gt;
        sidebarLanguageStatusRegistry = parsed &amp;amp;&amp;amp; typeof parsed === &#039;object&#039; ? parsed : {};&lt;br /&gt;
        sidebarLanguageStatusLoaded = true;&lt;br /&gt;
        sidebarLanguageStatusLoading = false;&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!force &amp;amp;&amp;amp; window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.fetchJsonRef === &#039;function&#039;) {&lt;br /&gt;
        window.EntryStore.fetchJsonRef(SIDEBAR_LANGUAGE_STATUS_TITLE, { noStore: false })&lt;br /&gt;
            .then(function (parsed) { finishLanguageStatus(parsed); })&lt;br /&gt;
            .catch(function () { finishLanguageStatus({}); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    (function () {&lt;br /&gt;
        var url = mw.util.getUrl(SIDEBAR_LANGUAGE_STATUS_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, SIDEBAR_LANGUAGE_STATUS_TITLE);&lt;br /&gt;
        }&lt;br /&gt;
        $.ajax({&lt;br /&gt;
            url: url,&lt;br /&gt;
            dataType: &#039;text&#039;,&lt;br /&gt;
            cache: true&lt;br /&gt;
        }).done(function(text) {&lt;br /&gt;
            var parsed = {};&lt;br /&gt;
&lt;br /&gt;
            try {&lt;br /&gt;
                parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                console.error(&#039;LanguageStatus.json parse failed:&#039;, err);&lt;br /&gt;
                parsed = {};&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            finishLanguageStatus(parsed);&lt;br /&gt;
        }).fail(function() {&lt;br /&gt;
            finishLanguageStatus({});&lt;br /&gt;
        });&lt;br /&gt;
    })();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.CLBI_LANGUAGE_STATUS = {&lt;br /&gt;
    title: SIDEBAR_LANGUAGE_STATUS_TITLE,&lt;br /&gt;
    languages: sidebarLanguageState.order.slice(),&lt;br /&gt;
    labels: SIDEBAR_LANGUAGE_LABELS,&lt;br /&gt;
    dialLabels: SIDEBAR_LANGUAGE_DIAL_LABELS,&lt;br /&gt;
    getPageKey: getSidebarLanguageStatusPageKey,&lt;br /&gt;
    getRegistry: function() {&lt;br /&gt;
        return sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    },&lt;br /&gt;
    reload: function(callback) {&lt;br /&gt;
        sidebarLanguageStatusLoaded = false;&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            renderSidebarLanguageBox();&lt;br /&gt;
            if (typeof callback === &#039;function&#039;) callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }, true);&lt;br /&gt;
    },&lt;br /&gt;
    refreshDial: function() {&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageMeta(lang) {&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var targetTitle = getLanguageTargetTitle(lang);&lt;br /&gt;
    var isCurrent = lang === currentLang;&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        lang: lang,&lt;br /&gt;
        code: getLangShortCode(lang),&lt;br /&gt;
        name: getSidebarLanguageName(lang),&lt;br /&gt;
        dialName: getSidebarLanguageDialName(lang),&lt;br /&gt;
        targetTitle: targetTitle,&lt;br /&gt;
        isCurrent: isCurrent,&lt;br /&gt;
        canMove: !!targetTitle &amp;amp;&amp;amp; !isCurrent&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatus(meta) {&lt;br /&gt;
    var override;&lt;br /&gt;
&lt;br /&gt;
    if (!meta) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (meta.isCurrent) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-current&#039;,&lt;br /&gt;
            label: &#039;CURRENT&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    override = getSidebarLanguageStatusOverride(meta.lang);&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;wip&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;WIP&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;unavailable&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;available&#039; || meta.targetTitle) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: meta.targetTitle ? &#039;is-ready&#039; : &#039;is-locked&#039;,&lt;br /&gt;
            label: meta.targetTitle ? &#039;AVAILABLE&#039; : &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: !!meta.targetTitle&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        className: &#039;is-locked&#039;,&lt;br /&gt;
        label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
        canApply: false&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguageRad(deg) {&lt;br /&gt;
    return (deg * Math.PI) / 180;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguagePointAt(radius, deg) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var angle = sidebarLanguageRad(deg);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        x: state.cx + Math.sin(angle) * radius,&lt;br /&gt;
        y: state.cy - Math.cos(angle) * radius&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageSectorPath(start, end) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var p1 = sidebarLanguagePointAt(state.outerR, start);&lt;br /&gt;
    var p2 = sidebarLanguagePointAt(state.outerR, end);&lt;br /&gt;
    var p3 = sidebarLanguagePointAt(state.innerR, end);&lt;br /&gt;
    var p4 = sidebarLanguagePointAt(state.innerR, start);&lt;br /&gt;
    var largeArc = Math.abs(end - start) &amp;gt; 180 ? 1 : 0;&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;M&#039;, p1.x.toFixed(3), p1.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.outerR, state.outerR, 0, largeArc, 1, p2.x.toFixed(3), p2.y.toFixed(3),&lt;br /&gt;
        &#039;L&#039;, p3.x.toFixed(3), p3.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.innerR, state.innerR, 0, largeArc, 0, p4.x.toFixed(3), p4.y.toFixed(3),&lt;br /&gt;
        &#039;Z&#039;&lt;br /&gt;
    ].join(&#039; &#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageShellPath() {&lt;br /&gt;
    return getSidebarLanguageSectorPath(-68, 68);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageByStep(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        index: index,&lt;br /&gt;
        meta: getSidebarLanguageMeta(state.order[index])&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewIndex() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    return normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewMeta() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    return getSidebarLanguageMeta(state.order[getSidebarLanguagePreviewIndex()]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeSidebarLanguageSector(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var item = getSidebarLanguageByStep(step);&lt;br /&gt;
    var group = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    var path = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    var label = createSidebarLanguageSvgEl(&#039;text&#039;);&lt;br /&gt;
    var labelY = state.cy - 78;&lt;br /&gt;
    var angle = step * state.sectorAngle;&lt;br /&gt;
&lt;br /&gt;
    group.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-group&#039;);&lt;br /&gt;
    group.setAttribute(&#039;data-step&#039;, String(step));&lt;br /&gt;
    group.setAttribute(&#039;data-index&#039;, String(item.index));&lt;br /&gt;
    group.setAttribute(&#039;data-lang&#039;, item.meta.lang);&lt;br /&gt;
    group.setAttribute(&#039;transform&#039;, &#039;rotate(&#039; + angle + &#039; &#039; + state.cx + &#039; &#039; + state.cy + &#039;)&#039;);&lt;br /&gt;
&lt;br /&gt;
    path.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector&#039;);&lt;br /&gt;
    path.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
&lt;br /&gt;
    label.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-label&#039;);&lt;br /&gt;
    label.setAttribute(&#039;x&#039;, String(state.cx));&lt;br /&gt;
    label.setAttribute(&#039;y&#039;, String(labelY + 5));&lt;br /&gt;
    label.textContent = item.meta.dialName || item.meta.name;&lt;br /&gt;
&lt;br /&gt;
    group.appendChild(path);&lt;br /&gt;
    group.appendChild(label);&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        if (sidebarLanguageState.dragging || performance.now() &amp;lt; sidebarLanguageState.suppressClickUntil) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        snapSidebarLanguageToStep(parseInt(group.getAttribute(&#039;data-step&#039;) || &#039;0&#039;, 10), true);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return group;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageWheel() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var svg;&lt;br /&gt;
    var defs;&lt;br /&gt;
    var clip;&lt;br /&gt;
    var clipPath;&lt;br /&gt;
    var shadowBlur;&lt;br /&gt;
    var blur;&lt;br /&gt;
    var fixedDepthGradient;&lt;br /&gt;
    var shell;&lt;br /&gt;
    var clipped;&lt;br /&gt;
    var rotor;&lt;br /&gt;
    var fixedDepthPath;&lt;br /&gt;
    var fixedFocus;&lt;br /&gt;
    var shadowSoft;&lt;br /&gt;
    var shadowHard;&lt;br /&gt;
    var rim;&lt;br /&gt;
    var pointer;&lt;br /&gt;
    var tri;&lt;br /&gt;
    var line;&lt;br /&gt;
    var step;&lt;br /&gt;
&lt;br /&gt;
    if (!fan) return;&lt;br /&gt;
&lt;br /&gt;
    fan.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    svg = createSidebarLanguageSvgEl(&#039;svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fan-svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 202 150&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;xMidYMid meet&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;role&#039;, &#039;img&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;aria-label&#039;, &#039;언어 선택 다이얼&#039;);&lt;br /&gt;
&lt;br /&gt;
    defs = createSidebarLanguageSvgEl(&#039;defs&#039;);&lt;br /&gt;
&lt;br /&gt;
    clip = createSidebarLanguageSvgEl(&#039;clipPath&#039;);&lt;br /&gt;
    clip.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fan-clip&#039;);&lt;br /&gt;
    clipPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    clipPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    clip.appendChild(clipPath);&lt;br /&gt;
&lt;br /&gt;
    shadowBlur = createSidebarLanguageSvgEl(&#039;filter&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-shadow-blur&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;x&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;y&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;width&#039;, &#039;140%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;height&#039;, &#039;140%&#039;);&lt;br /&gt;
    blur = createSidebarLanguageSvgEl(&#039;feGaussianBlur&#039;);&lt;br /&gt;
    blur.setAttribute(&#039;stdDeviation&#039;, &#039;3&#039;);&lt;br /&gt;
    shadowBlur.appendChild(blur);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthGradient = createSidebarLanguageSvgEl(&#039;linearGradient&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x2&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y2&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    [&lt;br /&gt;
        [&#039;0%&#039;, &#039;#ffffff&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;34%&#039;, &#039;#ffffff&#039;, &#039;0.006&#039;],&lt;br /&gt;
        [&#039;58%&#039;, &#039;#000000&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;100%&#039;, &#039;#000000&#039;, &#039;0.250&#039;]&lt;br /&gt;
    ].forEach(function(item) {&lt;br /&gt;
        var stop = createSidebarLanguageSvgEl(&#039;stop&#039;);&lt;br /&gt;
        stop.setAttribute(&#039;offset&#039;, item[0]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-color&#039;, item[1]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-opacity&#039;, item[2]);&lt;br /&gt;
        fixedDepthGradient.appendChild(stop);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    defs.appendChild(clip);&lt;br /&gt;
    defs.appendChild(shadowBlur);&lt;br /&gt;
    defs.appendChild(fixedDepthGradient);&lt;br /&gt;
    svg.appendChild(defs);&lt;br /&gt;
&lt;br /&gt;
    shell = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;class&#039;, &#039;sidebar-lang-shell&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shell);&lt;br /&gt;
&lt;br /&gt;
    clipped = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    clipped.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    rotor = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;class&#039;, &#039;sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
&lt;br /&gt;
    for (step = -state.repeats; step &amp;lt;= state.repeats; step += 1) {&lt;br /&gt;
        rotor.appendChild(makeSidebarLanguageSector(step));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    clipped.appendChild(rotor);&lt;br /&gt;
    svg.appendChild(clipped);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(fixedDepthPath);&lt;br /&gt;
&lt;br /&gt;
    fixedFocus = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-focus&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
    svg.appendChild(fixedFocus);&lt;br /&gt;
&lt;br /&gt;
    shadowSoft = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-soft&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowSoft);&lt;br /&gt;
&lt;br /&gt;
    shadowHard = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-hard&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowHard);&lt;br /&gt;
&lt;br /&gt;
    rim = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;class&#039;, &#039;sidebar-lang-rim&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(rim);&lt;br /&gt;
&lt;br /&gt;
    pointer = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-pointer&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    tri = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-triangle&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;d&#039;, &#039;M &#039; + (state.cx - 10) + &#039; 10 L &#039; + (state.cx + 10) + &#039; 10 L &#039; + state.cx + &#039; 26 Z&#039;);&lt;br /&gt;
    pointer.appendChild(tri);&lt;br /&gt;
&lt;br /&gt;
    line = createSidebarLanguageSvgEl(&#039;line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;x1&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;x2&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;y1&#039;, &#039;24&#039;);&lt;br /&gt;
    line.setAttribute(&#039;y2&#039;, &#039;112&#039;);&lt;br /&gt;
    pointer.appendChild(line);&lt;br /&gt;
&lt;br /&gt;
    svg.appendChild(pointer);&lt;br /&gt;
    fan.appendChild(svg);&lt;br /&gt;
&lt;br /&gt;
    state.rotor = rotor;&lt;br /&gt;
    setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateSidebarLanguageDial() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var meta = getSidebarLanguagePreviewMeta();&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
    var selectedValue = document.getElementById(&#039;clbi-sidebar-lang-selected-value&#039;);&lt;br /&gt;
    var availabilityPanel = document.getElementById(&#039;clbi-sidebar-lang-availability-panel&#039;);&lt;br /&gt;
    var availabilityValue = document.getElementById(&#039;clbi-sidebar-lang-availability-value&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (selectedValue) {&lt;br /&gt;
        selectedValue.textContent = meta.name;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityPanel) {&lt;br /&gt;
        availabilityPanel.classList.remove(&#039;is-ready&#039;, &#039;is-current&#039;, &#039;is-locked&#039;);&lt;br /&gt;
        availabilityPanel.classList.add(status.className);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityValue) {&lt;br /&gt;
        availabilityValue.textContent = status.label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (apply) {&lt;br /&gt;
        apply.classList.toggle(&#039;is-disabled&#039;, !status.canApply);&lt;br /&gt;
        apply.setAttribute(&#039;aria-disabled&#039;, status.canApply ? &#039;false&#039; : &#039;true&#039;);&lt;br /&gt;
        apply.setAttribute(&#039;aria-label&#039;, status.canApply ? (meta.name + &#039; 적용&#039;) : (meta.isCurrent ? &#039;현재 언어&#039; : &#039;사용할 수 없는 언어&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (selector) {&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-lang&#039;, meta.lang);&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-code&#039;, meta.code);&lt;br /&gt;
        selector.classList.toggle(&#039;is-current&#039;, meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-ready&#039;, status.canApply);&lt;br /&gt;
        selector.classList.toggle(&#039;is-locked&#039;, !status.canApply &amp;amp;&amp;amp; !meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-dragging&#039;, !!state.dragging);&lt;br /&gt;
        selector.classList.toggle(&#039;is-spinning&#039;, !!state.inertiaRaf);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        meta: meta,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageRotation(value, animate) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.rotation = value;&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (!state.rotor) return;&lt;br /&gt;
&lt;br /&gt;
    if (animate) {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-snapping&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.rotor.style.transform = &#039;rotate(&#039; + state.rotation.toFixed(3) + &#039;deg)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestSidebarLanguageRotation(value) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.pendingRotation = value;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) return;&lt;br /&gt;
&lt;br /&gt;
    state.raf = requestAnimationFrame(function() {&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
        setSidebarLanguageRotation(state.pendingRotation, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function cancelSidebarLanguageSpin() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    if (state.inertiaRaf) {&lt;br /&gt;
        cancelAnimationFrame(state.inertiaRaf);&lt;br /&gt;
        state.inertiaRaf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finishSidebarLanguageSnap(nearestIndex, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    state.baseIndex = normalizeSidebarLanguageIndex(nearestIndex);&lt;br /&gt;
    state.selectedIndex = state.baseIndex;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping is-dragging is-spinning&#039;);&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        callback(getSidebarLanguageMeta(state.order[state.selectedIndex]));&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageToStep(step, animate, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var targetRotation = -step * state.sectorAngle;&lt;br /&gt;
    var nearestIndex = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.selectedIndex = nearestIndex;&lt;br /&gt;
    setSidebarLanguageRotation(targetRotation, !!animate);&lt;br /&gt;
&lt;br /&gt;
    state.snapTimer = setTimeout(function() {&lt;br /&gt;
        finishSidebarLanguageSnap(nearestIndex, callback);&lt;br /&gt;
    }, animate ? 230 : 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageNearest(callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    snapSidebarLanguageToStep(step, true, callback);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function startSidebarLanguageInertiaSpin(initialVelocity) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var velocity;&lt;br /&gt;
    var lastFrame;&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
&lt;br /&gt;
    velocity = Math.max(-state.maxSpinVelocity, Math.min(state.maxSpinVelocity, initialVelocity));&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(velocity) &amp;lt; state.minSpinVelocity) {&lt;br /&gt;
        snapSidebarLanguageNearest();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-spinning&#039;);&lt;br /&gt;
    lastFrame = performance.now();&lt;br /&gt;
&lt;br /&gt;
    function frame(now) {&lt;br /&gt;
        var dt = Math.min(34, Math.max(1, now - lastFrame));&lt;br /&gt;
        var sign = velocity &amp;lt; 0 ? -1 : 1;&lt;br /&gt;
        var nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        state.rotation += velocity * dt;&lt;br /&gt;
        setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
&lt;br /&gt;
        nextSpeed = Math.max(0, Math.abs(velocity) - (state.spinDecel * dt));&lt;br /&gt;
        velocity = sign * nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        if (nextSpeed &amp;lt;= state.minSpinVelocity) {&lt;br /&gt;
            state.inertiaRaf = null;&lt;br /&gt;
            $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleSidebarLanguageNavigation(meta) {&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
&lt;br /&gt;
    if (!meta || !status.canApply) return;&lt;br /&gt;
&lt;br /&gt;
    clearTimeout(sidebarLanguageState.navigateTimer);&lt;br /&gt;
    sidebarLanguageState.navigateTimer = setTimeout(function() {&lt;br /&gt;
        var title = getLanguageTargetTitle(meta.lang);&lt;br /&gt;
&lt;br /&gt;
        if (!title || meta.lang === getCurrentLang()) return;&lt;br /&gt;
&lt;br /&gt;
        window.location.href = buildWikiPath(title);&lt;br /&gt;
    }, 70);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageSelection(lang) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = state.order.indexOf(lang);&lt;br /&gt;
&lt;br /&gt;
    if (index &amp;lt; 0) index = state.order.indexOf(getCurrentLang());&lt;br /&gt;
    if (index &amp;lt; 0) index = 0;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) {&lt;br /&gt;
        cancelAnimationFrame(state.raf);&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.currentLang = lang;&lt;br /&gt;
    state.baseIndex = index;&lt;br /&gt;
    state.selectedIndex = index;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
    state.dragMoved = false;&lt;br /&gt;
    state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveSidebarLanguageSelection(delta) {&lt;br /&gt;
    snapSidebarLanguageToStep(-delta, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindSidebarLanguageSelector() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!selector || !fan || !apply) return;&lt;br /&gt;
&lt;br /&gt;
    if (state.bound &amp;amp;&amp;amp; state.boundElement === selector) return;&lt;br /&gt;
&lt;br /&gt;
    state.bound = true;&lt;br /&gt;
    state.boundElement = selector;&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerdown&#039;, function(e) {&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
        state.dragging = true;&lt;br /&gt;
        state.dragMoved = false;&lt;br /&gt;
        state.dragStartX = e.clientX;&lt;br /&gt;
        state.dragStartY = e.clientY || 0;&lt;br /&gt;
        state.dragStartRotation = state.rotation;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = performance.now();&lt;br /&gt;
        state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
        selector.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
        selector.classList.remove(&#039;is-snapping&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        Vertical page scrolling must stay available when the pointer starts on&lt;br /&gt;
        the language dial. Capture and preventDefault are delayed until a&lt;br /&gt;
        horizontal drag is confirmed.&lt;br /&gt;
        */&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointermove&#039;, function(e) {&lt;br /&gt;
        var now;&lt;br /&gt;
        var totalDx;&lt;br /&gt;
        var totalDy;&lt;br /&gt;
        var frameDx;&lt;br /&gt;
        var dt;&lt;br /&gt;
        var instantVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        totalDx = e.clientX - state.dragStartX;&lt;br /&gt;
        totalDy = (e.clientY || 0) - state.dragStartY;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragAxis &amp;amp;&amp;amp; (Math.abs(totalDx) &amp;gt; 4 || Math.abs(totalDy) &amp;gt; 4)) {&lt;br /&gt;
            state.dragAxis = Math.abs(totalDx) &amp;gt;= Math.abs(totalDy) ? &#039;x&#039; : &#039;y&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (state.dragAxis === &#039;y&#039;) {&lt;br /&gt;
                state.dragging = false;&lt;br /&gt;
                state.dragMoved = false;&lt;br /&gt;
                state.dragAxis = null;&lt;br /&gt;
                state.pointerCaptured = false;&lt;br /&gt;
                selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (fan.setPointerCapture &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
                try {&lt;br /&gt;
                    fan.setPointerCapture(e.pointerId);&lt;br /&gt;
                    state.pointerCaptured = true;&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    state.pointerCaptured = false;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragAxis !== &#039;x&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        now = performance.now();&lt;br /&gt;
        frameDx = e.clientX - state.lastX;&lt;br /&gt;
        dt = Math.max(1, now - state.lastTime);&lt;br /&gt;
&lt;br /&gt;
        if (Math.abs(totalDx) &amp;gt; 3) state.dragMoved = true;&lt;br /&gt;
&lt;br /&gt;
        instantVelocity = (frameDx * state.dragSensitivity) / dt;&lt;br /&gt;
        state.releaseVelocity = (state.releaseVelocity * 0.62) + (instantVelocity * 0.38);&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = now;&lt;br /&gt;
&lt;br /&gt;
        requestSidebarLanguageRotation(state.dragStartRotation + totalDx * state.dragSensitivity);&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function finishDrag(e) {&lt;br /&gt;
        var velocityAge;&lt;br /&gt;
        var throwVelocity;&lt;br /&gt;
        var wasHorizontal;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        wasHorizontal = state.dragAxis === &#039;x&#039;;&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (fan.releasePointerCapture &amp;amp;&amp;amp; state.pointerCaptured &amp;amp;&amp;amp; e &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
            try { fan.releasePointerCapture(e.pointerId); } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
&lt;br /&gt;
        if (!wasHorizontal &amp;amp;&amp;amp; !state.dragMoved) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        velocityAge = performance.now() - state.lastTime;&lt;br /&gt;
        throwVelocity = velocityAge &amp;gt; 120 ? 0 : state.releaseVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(throwVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            startSidebarLanguageInertiaSpin(throwVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerup&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;pointercancel&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;lostpointercapture&#039;, function() {&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(state.releaseVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
            startSidebarLanguageInertiaSpin(state.releaseVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    apply.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        snapSidebarLanguageNearest(function(meta) {&lt;br /&gt;
            scheduleSidebarLanguageNavigation(meta);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    selector.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
        if (e.key === &#039;ArrowLeft&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(-1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;ArrowRight&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;Enter&#039; || e.key === &#039; &#039;) {&lt;br /&gt;
            apply.click();&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageBox() {&lt;br /&gt;
    bindSidebarLanguageSelector();&lt;br /&gt;
    setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
&lt;br /&gt;
    if (!sidebarLanguageStatusLoaded) {&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadRecentChangesList(targetSelector, limit) {&lt;br /&gt;
    var $target = $(targetSelector);&lt;br /&gt;
&lt;br /&gt;
    if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
    var isNewsList = $target.closest(&#039;.clbi-left-news-box&#039;).length &amp;gt; 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.html((t &amp;amp;&amp;amp; t.loading) ? t.loading : &#039;불러오는 중...&#039;);&lt;br /&gt;
&lt;br /&gt;
    $.getJSON(&lt;br /&gt;
        &#039;/api.php?action=query&amp;amp;list=recentchanges&amp;amp;rclimit=&#039; + encodeURIComponent(limit || 5) + &#039;&amp;amp;rcprop=title|timestamp|user&amp;amp;format=json&amp;amp;rcnamespace=0&amp;amp;rctype=edit|new&#039;,&lt;br /&gt;
        function(data) {&lt;br /&gt;
            var items = data &amp;amp;&amp;amp; data.query ? data.query.recentchanges : [];&lt;br /&gt;
            var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (!items || !items.length) {&lt;br /&gt;
                $target.html(&#039;표시할 변경 사항이 없습니다.&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $.each(items, function(i, item) {&lt;br /&gt;
                var label = timeAgo(item.timestamp);&lt;br /&gt;
                var title = item.title || &#039;&#039;;&lt;br /&gt;
                var userName = item.user || &#039;Unknown&#039;;&lt;br /&gt;
                var pageHref = buildWikiPath(title);&lt;br /&gt;
                var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(userName) + &#039;.png&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (isNewsList) {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;news-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;news-recent-avatar&amp;quot; src=&amp;quot;&#039; + escapeHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;news-recent-main&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-meta&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-user&amp;quot;&amp;gt;@&#039; + escapeHtml(userName) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
                } else {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;clbi-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;clbi-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList) {&lt;br /&gt;
                $target.html(&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-recent-viewport&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-recent-stack&amp;quot;&amp;gt;&#039; + html + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
                    ensureNewsBottomFinish();&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                $target.html(html);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList &amp;amp;&amp;amp; typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
                scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $target.find(isNewsList ? &#039;.news-recent-item&#039; : &#039;.clbi-recent-item&#039;).each(function() {&lt;br /&gt;
                var wrap = $(this).find(isNewsList ? &#039;.news-recent-title-wrap&#039; : &#039;.clbi-recent-title-wrap&#039;);&lt;br /&gt;
                var title = $(this).find(isNewsList ? &#039;.news-recent-title&#039; : &#039;.clbi-recent-title&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (!wrap.length || !title.length) return;&lt;br /&gt;
&lt;br /&gt;
                var wrapW = wrap.width();&lt;br /&gt;
                var titleW = title[0].scrollWidth;&lt;br /&gt;
&lt;br /&gt;
                if (titleW &amp;gt; wrapW + 20) {&lt;br /&gt;
                    var duration = titleW / 40;&lt;br /&gt;
&lt;br /&gt;
                    title.css({&lt;br /&gt;
                        animation: &#039;clbi-scroll &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
                        &#039;--scroll-dist&#039;: &#039;-&#039; + (titleW - wrapW + 8) + &#039;px&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    ).fail(function() {&lt;br /&gt;
        var lang = getCurrentLang();&lt;br /&gt;
        var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
&lt;br /&gt;
        $target.html((t &amp;amp;&amp;amp; t.loadFail) ? t.loadFail : &#039;불러오기 실패&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureRecentViewport() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var viewport;&lt;br /&gt;
    var stack;&lt;br /&gt;
    var children;&lt;br /&gt;
&lt;br /&gt;
    if (!list) return null;&lt;br /&gt;
&lt;br /&gt;
    viewport = list.querySelector(&#039;:scope &amp;gt; .news-recent-viewport&#039;);&lt;br /&gt;
    stack = viewport ? viewport.querySelector(&#039;:scope &amp;gt; .news-recent-stack&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (viewport &amp;amp;&amp;amp; stack) return viewport;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(list.children || []);&lt;br /&gt;
&lt;br /&gt;
    viewport = document.createElement(&#039;div&#039;);&lt;br /&gt;
    viewport.className = &#039;news-recent-viewport&#039;;&lt;br /&gt;
&lt;br /&gt;
    stack = document.createElement(&#039;div&#039;);&lt;br /&gt;
    stack.className = &#039;news-recent-stack&#039;;&lt;br /&gt;
&lt;br /&gt;
    children.forEach(function (child) {&lt;br /&gt;
        if (child.classList &amp;amp;&amp;amp; child.classList.contains(&#039;news-recent-viewport&#039;)) return;&lt;br /&gt;
        stack.appendChild(child);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    viewport.appendChild(stack);&lt;br /&gt;
    list.appendChild(viewport);&lt;br /&gt;
&lt;br /&gt;
    return viewport;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ensureNewsBottomFinish() {&lt;br /&gt;
    var newsBox = document.querySelector(&#039;#clbi-left-sidebar .clbi-left-news-box&#039;);&lt;br /&gt;
    var content = newsBox ? newsBox.querySelector(&#039;.clbi-news-box&#039;) : null;&lt;br /&gt;
    var finish;&lt;br /&gt;
&lt;br /&gt;
    if (!content) return null;&lt;br /&gt;
&lt;br /&gt;
    finish = content.querySelector(&#039;:scope &amp;gt; .news-bottom-finish&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!finish) {&lt;br /&gt;
        finish = document.createElement(&#039;div&#039;);&lt;br /&gt;
        finish.className = &#039;news-bottom-finish&#039;;&lt;br /&gt;
        finish.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        content.appendChild(finish);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return finish;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateAdaptiveLeftRecentItems() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: 좌측 사이드는 뉴스 확장형 flex 레이아웃이 높이를 담당한다.&lt;br /&gt;
    이전 adaptive 코드는 항목을 숨기거나 mask/fade를 걸기 위한 것이었으므로&lt;br /&gt;
    여기서는 잔여 상태만 정리하고 DOM 래퍼만 보장한다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftRecentAdaptiveState();&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureRecentViewport === &#039;function&#039;) {&lt;br /&gt;
        ensureRecentViewport();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
        ensureNewsBottomFinish();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleAdaptiveLeftRecentItems() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateAdaptiveLeftRecentItems();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 80);&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateClbiContentBottomGap(iteration) {&lt;br /&gt;
    var content = document.querySelector(&#039;.container-fluid.liberty-content&#039;);&lt;br /&gt;
    var main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var desiredGap = 8;&lt;br /&gt;
    var rootStyle;&lt;br /&gt;
    var scale = 1;&lt;br /&gt;
    var contentRect;&lt;br /&gt;
    var bottomTop;&lt;br /&gt;
    var targetHeight;&lt;br /&gt;
    var currentHeight;&lt;br /&gt;
    var visualGap;&lt;br /&gt;
&lt;br /&gt;
    iteration = iteration || 0;&lt;br /&gt;
&lt;br /&gt;
    if (!content || !main || !bottomNav) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    하단 간격은 scale 모드에서도 같은 기준으로 계산한다.&lt;br /&gt;
    transform:scale()이 걸리면 getBoundingClientRect()는 축소된 화면 좌표를 반환하므로,&lt;br /&gt;
    목표 간격 8px도 scale을 곱한 화면 좌표로 비교하고 다시 design px로 환산한다.&lt;br /&gt;
&lt;br /&gt;
    목표:&lt;br /&gt;
    .liberty-content-main.bottom === #clbi-bottom-nav-wrap.top - 8px&lt;br /&gt;
    */&lt;br /&gt;
    if (document.body &amp;amp;&amp;amp; document.body.classList &amp;amp;&amp;amp; document.body.classList.contains(&#039;clbi-shell-vertical-scale&#039;)) {&lt;br /&gt;
        rootStyle = window.getComputedStyle(document.documentElement);&lt;br /&gt;
        scale = parseFloat(rootStyle.getPropertyValue(&#039;--clbi-shell-scale&#039;)) || 1;&lt;br /&gt;
        scale = Math.max(0.25, scale);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    contentRect = content.getBoundingClientRect();&lt;br /&gt;
    bottomTop = bottomNav.getBoundingClientRect().top;&lt;br /&gt;
    visualGap = desiredGap * scale;&lt;br /&gt;
    targetHeight = Math.floor((bottomTop - contentRect.top - visualGap) / scale);&lt;br /&gt;
    targetHeight = Math.max(120, targetHeight);&lt;br /&gt;
&lt;br /&gt;
    currentHeight = Math.round(content.getBoundingClientRect().height / scale);&lt;br /&gt;
&lt;br /&gt;
    content.style.setProperty(&#039;--clbi-content-extra&#039;, &#039;0px&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;max-height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(currentHeight - targetHeight) &amp;gt;= 1 &amp;amp;&amp;amp; iteration &amp;lt; 4) {&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            updateClbiContentBottomGap(iteration + 1);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiContentBottomGap() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 40);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 120);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 280);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 520);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateLeftBillboardAdaptive() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: Ad는 이미지/CRT 비율을 유지하는 고정 슬롯이다.&lt;br /&gt;
    남는 세로 공간은 뉴스 박스가 흡수하므로, Ad에 하단 finish를 늘리거나&lt;br /&gt;
    title-only 상태로 접는 adaptive 보정은 사용하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftBillboardAdaptiveState();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftBillboardAdaptive() {&lt;br /&gt;
    window.requestAnimationFrame(updateLeftBillboardAdaptive);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 80);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftSidebarVerticalFit() {&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
            scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
            scheduleLeftBillboardAdaptive();&lt;br /&gt;
        }&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시대 문서 전용 왼쪽 사이드바 이미지&lt;br /&gt;
function updateLeftSidebarNationsImage() {&lt;br /&gt;
    $(&#039;#clbi-left-nations-image&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setProfileActionLabel(selector, text) {&lt;br /&gt;
    var target = $(selector);&lt;br /&gt;
    var label = target.find(&#039;.profile-action-label&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (label.length) {&lt;br /&gt;
        label.text(text);&lt;br /&gt;
    } else {&lt;br /&gt;
        target.text(text);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 사이드바 업데이트&lt;br /&gt;
function updateSidebar() {&lt;br /&gt;
    if (!window.LANG) {&lt;br /&gt;
        setTimeout(updateSidebar, 100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
&lt;br /&gt;
    var newsTitle = t.news || &#039;뉴스&#039;;&lt;br /&gt;
    var changelogTitle = t.changelog || &#039;체인지로그&#039;;&lt;br /&gt;
    var recentTitle = t.recentChanges || &#039;최근 변경&#039;;&lt;br /&gt;
    var languageTitle = t.language || &#039;언어&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-language&#039;).text(languageTitle);&lt;br /&gt;
    renderSidebarLanguageBox();&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-news&#039;).text(newsTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-recent-main&#039;).text(recentTitle);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-search a&#039;).text(t.search);&lt;br /&gt;
    $(&#039;#clbi-search-input&#039;).attr(&#039;placeholder&#039;, t.search + &#039;...&#039;);&lt;br /&gt;
    $(&#039;#clbi-title-recent a&#039;).text(recentTitle);&lt;br /&gt;
    $(&#039;#clbi-title-guide-label&#039;).text(t.guide);&lt;br /&gt;
    $(&#039;#clbi-guide-link&#039;).text(t.getStarted);&lt;br /&gt;
    $(&#039;#clbi-title-links-label&#039;).text(t.links);&lt;br /&gt;
&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-contribution&#039;, t.contribution);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-watchlist&#039;, t.watchlist);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-preferences&#039;, t.preferences);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-logout&#039;, t.logout);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-login&#039;, t.login);&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var specialPage = String(mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
$(&#039;#clbi-left-news-recent-title&#039;).text(&#039;RECENT CHANGES&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.clbi-user-btn&#039;).removeClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;Contributions&#039; ||&lt;br /&gt;
        specialPage === &#039;기여&#039; ||&lt;br /&gt;
        pageName.indexOf(&#039;특수:기여&#039;) === 0 ||&lt;br /&gt;
        pageName.indexOf(&#039;Special:Contributions&#039;) === 0&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-contribution&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (specialPage === &#039;Watchlist&#039;) {&lt;br /&gt;
        $(&#039;#clbi-btn-watchlist&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;설정&#039; ||&lt;br /&gt;
        pageName === &#039;특수:설정&#039; ||&lt;br /&gt;
        pageName === &#039;Special:설정&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-preferences&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.toggleBtn&#039;).each(function() {&lt;br /&gt;
        var btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        if (!$(&#039;#&#039; + btn.data(&#039;target&#039;)).hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            btn.text(t.expand);&lt;br /&gt;
        } else {&lt;br /&gt;
            btn.text(t.collapse);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    updateLeftSidebarNationsImage();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function canShowContentTools() {&lt;br /&gt;
    // 비로그인 사용자는 편집/역사/공유 버튼을 숨김&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // MediaWiki가 현재 문서를 편집 가능하지 않다고 판단하면 숨김&lt;br /&gt;
    var isEditable = mw.config.get(&#039;wgIsProbablyEditable&#039;);&lt;br /&gt;
    if (isEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var relevantEditable = mw.config.get(&#039;wgRelevantPageIsProbablyEditable&#039;);&lt;br /&gt;
    if (relevantEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkNodes(root) {&lt;br /&gt;
    var seen = [];&lt;br /&gt;
    var nodes = [];&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
&lt;br /&gt;
    $root.find(&#039;#catlinks, .catlinks&#039;).add($root.filter(&#039;#catlinks, .catlinks&#039;)).each(function () {&lt;br /&gt;
        if (seen.indexOf(this) !== -1) return;&lt;br /&gt;
        seen.push(this);&lt;br /&gt;
        nodes.push(this);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return nodes;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinksTarget(root) {&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
    var parserOutput = $root.find(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
    var main = $root.find(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.liberty-content-main&#039;)) {&lt;br /&gt;
        parserOutput = $(root).find(&#039;.mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.mw-parser-output&#039;)) {&lt;br /&gt;
        parserOutput = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (parserOutput.length) return parserOutput;&lt;br /&gt;
    if (main.length) return main;&lt;br /&gt;
&lt;br /&gt;
    if (!root) {&lt;br /&gt;
        parserOutput = $(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
        if (parserOutput.length) return parserOutput;&lt;br /&gt;
        if (main.length) return main;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return $();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_CATLINKS_FETCH_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function getCurrentPageTitleForCatlinks() {&lt;br /&gt;
    return String(&lt;br /&gt;
        mw.config.get(&#039;wgPageName&#039;) ||&lt;br /&gt;
        mw.config.get(&#039;wgRelevantPageName&#039;) ||&lt;br /&gt;
        &#039;&#039;&lt;br /&gt;
    ).trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function shouldFetchCatlinks() {&lt;br /&gt;
    var pageName = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) return false;&lt;br /&gt;
    if (specialPage) return false;&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearCatlinksInlineHiding(cat) {&lt;br /&gt;
    if (!cat || !cat.style) return;&lt;br /&gt;
&lt;br /&gt;
    cat.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-normal-catlinks, #mw-normal-catlinks, .mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        if (!this.style) return;&lt;br /&gt;
        this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function exposeHiddenCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        this.classList.remove(&#039;mw-hidden-cats-hidden&#039;);&lt;br /&gt;
        this.classList.add(&#039;mw-hidden-cats-user-shown&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (this.style) {&lt;br /&gt;
            this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkTextContent(cat) {&lt;br /&gt;
    var clone;&lt;br /&gt;
    var text;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    clone = cat.cloneNode(true);&lt;br /&gt;
    $(clone).find(&#039;script, style&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    text = String(clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*:\s*/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*$/i, &#039;&#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function hasRenderedCatlinkItems(cat) {&lt;br /&gt;
    var hasLink;&lt;br /&gt;
    var hasListText;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    hasLink = false;&lt;br /&gt;
    $(cat).find(&#039;a&#039;).each(function () {&lt;br /&gt;
        var text = String($(this).text() || &#039;&#039;).trim();&lt;br /&gt;
        var href = String(this.getAttribute(&#039;href&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        if (text || href) hasLink = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasLink) return true;&lt;br /&gt;
&lt;br /&gt;
    hasListText = false;&lt;br /&gt;
    $(cat).find(&#039;li&#039;).each(function () {&lt;br /&gt;
        if (String($(this).text() || &#039;&#039;).trim()) hasListText = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasListText) return true;&lt;br /&gt;
&lt;br /&gt;
    return !!getCatlinkTextContent(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCategoryTitle(rawTitle) {&lt;br /&gt;
    var title = String(rawTitle == null ? &#039;&#039; : rawTitle).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!title) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    title = title.replace(/_/g, &#039; &#039;);&lt;br /&gt;
&lt;br /&gt;
    if (/^(Category|분류):/i.test(title)) {&lt;br /&gt;
        return title;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;분류:&#039; + title;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeCategoryLinkTitle(rawTitle) {&lt;br /&gt;
    return String(rawTitle || &#039;&#039;)&lt;br /&gt;
        .replace(/^Category:/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^분류:/, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function dedupeCatlinkCategories(categories) {&lt;br /&gt;
    var seen = {};&lt;br /&gt;
    var result = [];&lt;br /&gt;
&lt;br /&gt;
    (categories || []).forEach(function (item) {&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var hidden = false;&lt;br /&gt;
&lt;br /&gt;
        if (typeof item === &#039;string&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item);&lt;br /&gt;
        } else if (item &amp;amp;&amp;amp; typeof item === &#039;object&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item.title || item.name || item.category || &#039;&#039;);&lt;br /&gt;
            hidden = item.hidden !== undefined || item.isHidden === true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
        if (seen[title]) return;&lt;br /&gt;
&lt;br /&gt;
        seen[title] = true;&lt;br /&gt;
        result.push({ title: title, hidden: hidden });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getConfigCatlinksCategories() {&lt;br /&gt;
    var normal = mw.config.get(&#039;wgCategories&#039;) || [];&lt;br /&gt;
    var hidden = mw.config.get(&#039;wgHiddenCategories&#039;) || [];&lt;br /&gt;
    var categories = [];&lt;br /&gt;
&lt;br /&gt;
    if (!Array.isArray(normal)) normal = [];&lt;br /&gt;
    if (!Array.isArray(hidden)) hidden = [];&lt;br /&gt;
&lt;br /&gt;
    normal.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: false });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    hidden.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: true });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return dedupeCatlinkCategories(categories);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksReady(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksEmpty(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksPending(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-fetching&#039;, &#039;1&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderFetchedCatlinks(cat, categories, pageTitle) {&lt;br /&gt;
    var container;&lt;br /&gt;
    var ul;&lt;br /&gt;
    var normalized;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    normalized = dedupeCatlinkCategories(categories);&lt;br /&gt;
&lt;br /&gt;
    if (!normalized.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cat.innerHTML = &#039;&#039;;&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-api-populated&#039;);&lt;br /&gt;
&lt;br /&gt;
    container = document.createElement(&#039;div&#039;);&lt;br /&gt;
    container.className = &#039;mw-normal-catlinks&#039;;&lt;br /&gt;
    container.appendChild(document.createTextNode(&#039;분류: &#039;));&lt;br /&gt;
&lt;br /&gt;
    ul = document.createElement(&#039;ul&#039;);&lt;br /&gt;
&lt;br /&gt;
    normalized.forEach(function (item) {&lt;br /&gt;
        var title = String(item &amp;amp;&amp;amp; item.title ? item.title : &#039;&#039;).trim();&lt;br /&gt;
        var li;&lt;br /&gt;
        var a;&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
&lt;br /&gt;
        li = document.createElement(&#039;li&#039;);&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = mw.util.getUrl(title);&lt;br /&gt;
        a.title = title;&lt;br /&gt;
        a.textContent = makeCategoryLinkTitle(title);&lt;br /&gt;
&lt;br /&gt;
        if (item.hidden) {&lt;br /&gt;
            li.className = &#039;clbi-hidden-category-item&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        li.appendChild(a);&lt;br /&gt;
        ul.appendChild(li);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (!ul.children.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.appendChild(ul);&lt;br /&gt;
    cat.appendChild(container);&lt;br /&gt;
    markCatlinksReady(cat, pageTitle);&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksForPage(pageTitle, callback) {&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (typeof pageTitle === &#039;function&#039;) {&lt;br /&gt;
        callback = pageTitle;&lt;br /&gt;
        pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    pageTitle = String(pageTitle || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!pageTitle || !shouldFetchCatlinks()) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;categories&#039;,&lt;br /&gt;
        titles: pageTitle,&lt;br /&gt;
        cllimit: &#039;max&#039;,&lt;br /&gt;
        clprop: &#039;hidden&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).done(function (data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages &amp;amp;&amp;amp; pages.length ? pages[0] : null;&lt;br /&gt;
        var categories = page &amp;amp;&amp;amp; page.categories ? page.categories : [];&lt;br /&gt;
&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback(categories || [], pageTitle);&lt;br /&gt;
    }).fail(function () {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finalizeEmptyCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    markCatlinksEmpty(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksIfNeeded(cat) {&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(cat, configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) {&lt;br /&gt;
        finalizeEmptyCatlinks(cat);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (cat.getAttribute(&#039;data-clbi-catlinks-fetching&#039;) === &#039;1&#039; &amp;amp;&amp;amp; cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) === pageTitle) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
    markCatlinksPending(cat, pageTitle);&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (!cat || !cat.isConnected) return;&lt;br /&gt;
        if (cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) !== requestedPage) return;&lt;br /&gt;
&lt;br /&gt;
        if (!renderFetchedCatlinks(cat, categories, requestedPage)) {&lt;br /&gt;
            finalizeEmptyCatlinks(cat);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCatlinksPanel(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksIfNeeded(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createCatlinksPanel(target, className) {&lt;br /&gt;
    var cat;&lt;br /&gt;
&lt;br /&gt;
    if (!target || !target.length) return null;&lt;br /&gt;
&lt;br /&gt;
    cat = document.createElement(&#039;div&#039;);&lt;br /&gt;
    cat.id = &#039;catlinks&#039;;&lt;br /&gt;
    cat.className = className || &#039;catlinks clbi-catlinks-created clbi-catlinks-pending&#039;;&lt;br /&gt;
    target.append(cat);&lt;br /&gt;
    return cat;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prepareSpaCatlinksBeforeInsert(root) {&lt;br /&gt;
    var nodes;&lt;br /&gt;
    var target;&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    nodes = getCatlinkNodes(root);&lt;br /&gt;
&lt;br /&gt;
    nodes.forEach(function (node) {&lt;br /&gt;
        node.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
        clearCatlinksInlineHiding(node);&lt;br /&gt;
        exposeHiddenCatlinks(node);&lt;br /&gt;
&lt;br /&gt;
        if (hasRenderedCatlinkItems(node)) {&lt;br /&gt;
            markCatlinksReady(node, pageTitle);&lt;br /&gt;
        } else if (configCategories.length) {&lt;br /&gt;
            renderFetchedCatlinks(node, configCategories, pageTitle);&lt;br /&gt;
        } else {&lt;br /&gt;
            markCatlinksEmpty(node);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (nodes.length || !configCategories.length) return;&lt;br /&gt;
&lt;br /&gt;
    target = getCatlinksTarget(root);&lt;br /&gt;
    if (!target.length) target = $(root);&lt;br /&gt;
&lt;br /&gt;
    renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveCatlinksToBottom() {&lt;br /&gt;
    var main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
    var target = getCatlinksTarget();&lt;br /&gt;
    var catlinks = getCatlinkNodes();&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!main.length || !target.length) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    catlinks.forEach(function (node) {&lt;br /&gt;
        var catNode = $(node);&lt;br /&gt;
&lt;br /&gt;
        if (node.parentNode !== target[0]) {&lt;br /&gt;
            catNode.appendTo(target);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        normalizeCatlinksPanel(node);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (catlinks.length) return;&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        var cat;&lt;br /&gt;
&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (getCatlinkNodes().length) return;&lt;br /&gt;
        if (!categories || !categories.length) return;&lt;br /&gt;
&lt;br /&gt;
        cat = createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;);&lt;br /&gt;
        renderFetchedCatlinks(cat, categories, requestedPage);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 대문 스타일&lt;br /&gt;
function initCategoryNavIfAvailable(root) {&lt;br /&gt;
    /*&lt;br /&gt;
    CategoryNav.js는 대문 카테고리 네비를 SVG로 생성한다.&lt;br /&gt;
&lt;br /&gt;
    Common.js가 SPA로 본문을 갈아끼운 뒤에는 MediaWiki 원래 페이지 로드와 달리&lt;br /&gt;
    CategoryNav.js의 초기 DOMContentLoaded만으로는 새 mount를 다시 잡지 못할 수 있다.&lt;br /&gt;
    CategoryNav.js 자체도 mw.hook(&#039;wikipage.content&#039;)를 듣지만, 로드 순서와 SPA 타이밍이&lt;br /&gt;
    엇갈릴 수 있으므로 Common.js 쪽에서도 존재 여부를 확인한 뒤 한 번 더 호출한다.&lt;br /&gt;
&lt;br /&gt;
    이 함수는 CategoryNav.js가 아직 로드되지 않았으면 아무 것도 하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    if (&lt;br /&gt;
        window.CLBI &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI.categoryNav &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI.categoryNav.init === &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        window.CLBI.categoryNav.init(root || document);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeLegacyMainPageHero() {&lt;br /&gt;
    /*&lt;br /&gt;
    기존 대문 전용 레거시 요소 정리&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    이전 대문 구조에서는 Common.js가 본문 바깥에 #clbi-main-logo를 직접 삽입하고,&lt;br /&gt;
    본문 안의 #clbi-main-crt-hero를 #clbi-main-crt-hero-wrap으로 감싸서&lt;br /&gt;
    .liberty-content-main 위쪽으로 재배치했다.&lt;br /&gt;
&lt;br /&gt;
    새 대문은 본문 내부의 .main-portal이 로고, 알림, 카테고리 네비, 이미지 피드,&lt;br /&gt;
    방명록, 상태 패널을 모두 담당한다. 따라서 Common.js가 별도 로고나 CRT 래퍼를&lt;br /&gt;
    삽입하면 새 로고/콘텐츠와 중복된다.&lt;br /&gt;
&lt;br /&gt;
    여기서는 JS가 만들던 바깥 로고와 CRT 래퍼를 제거하고, 예전 대문 원본이나&lt;br /&gt;
    캐시된 렌더 결과에 남아 있을 수 있는 #clbi-main-crt-hero도 제거한다.&lt;br /&gt;
    */&lt;br /&gt;
    $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function setNativePageTitleHiddenHard(hidden) {&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    document.querySelectorAll(selectors.join(&#039;,&#039;)).forEach(function(node) {&lt;br /&gt;
        if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
        if (hidden) {&lt;br /&gt;
            node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;visibility&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;min-height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;margin&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;padding&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;overflow&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
        } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;min-height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;margin&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;padding&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyDefaultPageTitleVisibility() {&lt;br /&gt;
    var hideTitle = true;&lt;br /&gt;
    var isSystemAssetPage = false;&lt;br /&gt;
&lt;br /&gt;
    if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isSystemAssetPage === &#039;function&#039;) {&lt;br /&gt;
        isSystemAssetPage = window.CLBI_PAGE_SHELL.isSystemAssetPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage) {&lt;br /&gt;
        hideTitle = true;&lt;br /&gt;
    } else if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isBackendOrSystemPage === &#039;function&#039;) {&lt;br /&gt;
        hideTitle = !window.CLBI_PAGE_SHELL.isBackendOrSystemPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;)&lt;br /&gt;
        .toggleClass(&#039;page-title-hidden&#039;, hideTitle)&lt;br /&gt;
        .toggleClass(&#039;page-title-visible&#039;, !hideTitle)&lt;br /&gt;
        .toggleClass(&#039;clbi-system-doc-page&#039;, isSystemAssetPage);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.renderSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.renderSystemDocIndicator();&lt;br /&gt;
    } else if (!isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.removeSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.removeSystemDocIndicator();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (hideTitle) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(false);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyMainPageStyle() {&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    if (specialPage === &#039;Preferences&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var namespaceNumber = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var isMainPage = (pageName === &#039;대문&#039;);&lt;br /&gt;
    var isUserProfilePage = (namespaceNumber === 2);&lt;br /&gt;
    var isScreenDoc = ($(&#039;.screen-header&#039;).length &amp;gt; 0);&lt;br /&gt;
    var hideTools = (isMainPage || isUserProfilePage || !canShowContentTools());&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, isUserProfilePage);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;clbi-main-page&#039;, isMainPage);&lt;br /&gt;
&lt;br /&gt;
    // 모든 문서에서 분류 바를 본문 컨테이너 아래로 이동&lt;br /&gt;
    moveCatlinksToBottom();&lt;br /&gt;
&lt;br /&gt;
    if (isMainPage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 새 대문은 .main-portal 본문 구조가 로고/히어로를 담당한다.&lt;br /&gt;
        // Common.js의 구식 바깥 로고/CRT 재배치 루틴은 사용하지 않는다.&lt;br /&gt;
        removeLegacyMainPageHero();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    } else if (isUserProfilePage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else if (isScreenDoc) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        if ($(&#039;#clbi-tools-box&#039;).length === 0 &amp;amp;&amp;amp; canShowContentTools()) {&lt;br /&gt;
            var $toolsBox = $(&#039;&amp;lt;div id=&amp;quot;clbi-tools-box&amp;quot; class=&amp;quot;clbi-left-box&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsTitle = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;관리&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsContent = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-content&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
            $toolsContent.append($(&#039;.content-tools .btn-group&#039;).clone(true));&lt;br /&gt;
            $toolsBox.append($toolsTitle).append($toolsContent);&lt;br /&gt;
            $(&#039;#clbi-left-sidebar&#039;).append($toolsBox);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!isUserProfilePage) {&lt;br /&gt;
        $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
        $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    applyDefaultPageTitleVisibility();&lt;br /&gt;
    updateSidebar();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 본문 기본 목차 제거&lt;br /&gt;
function removeNativeTocFromContent() {&lt;br /&gt;
    $(&#039;.liberty-content-main #toc, .liberty-content-main .toc&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 ID 가져오기&lt;br /&gt;
function getHeadingId(heading) {&lt;br /&gt;
    if (heading.id) {&lt;br /&gt;
        return heading.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline[id]&#039;);&lt;br /&gt;
    if (headline &amp;amp;&amp;amp; headline.id) {&lt;br /&gt;
        return headline.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 제목 텍스트 가져오기&lt;br /&gt;
function getHeadingText(heading) {&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline&#039;);&lt;br /&gt;
    var source = headline || heading;&lt;br /&gt;
    var clone = source.cloneNode(true);&lt;br /&gt;
&lt;br /&gt;
    $(clone).find(&#039;.mw-editsection, .mw-editsection-bracket, .mw-editsection-divider&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    return (clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: 긴 제목에 자동 스크롤 적용&lt;br /&gt;
function initTocTitleScroll(root) {&lt;br /&gt;
    var $items = root&lt;br /&gt;
        ? $(root).find(&#039;.toc-scroll-text&#039;)&lt;br /&gt;
        : $(&#039;#side-toc-box .toc-scroll-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    $items.each(function () {&lt;br /&gt;
        var $text = $(this);&lt;br /&gt;
        var $wrap = $text.closest(&#039;.toc-scroll-wrap&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$wrap.length) return;&lt;br /&gt;
&lt;br /&gt;
        var wrapW = Math.floor($wrap.width());&lt;br /&gt;
        var textW = Math.ceil(this.scrollWidth);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 레이아웃 계산이 끝나지 않았으면 이번 실행에서는 건드리지 않는다.&lt;br /&gt;
        if (!wrapW || !textW) return;&lt;br /&gt;
&lt;br /&gt;
        if (textW &amp;lt;= wrapW + 12) {&lt;br /&gt;
            // 왼쪽 목차: 칸을 넘지 않는 제목은 전체 텍스트를 그대로 보여준다.&lt;br /&gt;
            $wrap.removeClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
            if ($text.data(&#039;toc-scroll-enabled&#039;)) {&lt;br /&gt;
                $text.css({&lt;br /&gt;
                    animation: &#039;&#039;,&lt;br /&gt;
                    &#039;animation-delay&#039;: &#039;&#039;,&lt;br /&gt;
                    &#039;--scroll-dist&#039;: &#039;&#039;&lt;br /&gt;
                });&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-enabled&#039;);&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-key&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var scrollDist = &#039;-&#039; + (textW - wrapW + 10) + &#039;px&#039;;&lt;br /&gt;
        var duration = Math.max(7, textW / 38) * 1.25;&lt;br /&gt;
        var scrollKey = scrollDist + &#039;|&#039; + duration;&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목에는 오른쪽 페이드와 스크롤을 적용한다.&lt;br /&gt;
        $wrap.addClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 같은 값으로 이미 적용된 애니메이션은 다시 초기화하지 않는다.&lt;br /&gt;
        if ($text.data(&#039;toc-scroll-key&#039;) === scrollKey) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $text.data(&#039;toc-scroll-enabled&#039;, true);&lt;br /&gt;
        $text.data(&#039;toc-scroll-key&#039;, scrollKey);&lt;br /&gt;
&lt;br /&gt;
        $text.css({&lt;br /&gt;
            // 왼쪽 목차: 페이지 진입 직후에는 잠시 읽을 시간을 준 뒤 흐르게 한다.&lt;br /&gt;
            animation: &#039;toc-scroll-blink-reset &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
            &#039;animation-delay&#039;: &#039;1s&#039;,&lt;br /&gt;
            &#039;--scroll-dist&#039;: scrollDist&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 목차를 왼쪽 사이드바에 새로 생성&lt;br /&gt;
function moveTocToLeftSidebar() {&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: MediaWiki가 만든 원래 목차는 본문에서 제거한다.&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
&lt;br /&gt;
    var leftSidebar = document.getElementById(&#039;clbi-left-sidebar&#039;);&lt;br /&gt;
    if (!leftSidebar) return;&lt;br /&gt;
&lt;br /&gt;
    var content =&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;) ||&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!content) return;&lt;br /&gt;
&lt;br /&gt;
    var headings = Array.prototype.slice.call(&lt;br /&gt;
        content.querySelectorAll(&#039;h2, h3&#039;)&lt;br /&gt;
    ).filter(function (heading) {&lt;br /&gt;
        if (heading.closest(&#039;#toc, .toc, #side-toc-box&#039;)) return false;&lt;br /&gt;
&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
&lt;br /&gt;
        if (!id || !text) return false;&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var tocKey = headings.map(function (heading) {&lt;br /&gt;
        return getHeadingId(heading) + &#039;|&#039; + getHeadingText(heading);&lt;br /&gt;
    }).join(&#039;||&#039;);&lt;br /&gt;
&lt;br /&gt;
    var existingBox = document.getElementById(&#039;side-toc-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 같은 문서에서 같은 목차를 이미 만들었다면 다시 지우고 만들지 않는다.&lt;br /&gt;
    if (existingBox &amp;amp;&amp;amp; existingBox.getAttribute(&#039;data-toc-key&#039;) === tocKey) {&lt;br /&gt;
        initTocTitleScroll(existingBox);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (existingBox) {&lt;br /&gt;
        existingBox.remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!headings.length) return;&lt;br /&gt;
&lt;br /&gt;
    var tocBox = document.createElement(&#039;div&#039;);&lt;br /&gt;
    tocBox.className = &#039;clbi-left-box&#039;;&lt;br /&gt;
    tocBox.id = &#039;side-toc-box&#039;;&lt;br /&gt;
    tocBox.setAttribute(&#039;data-toc-key&#039;, tocKey);&lt;br /&gt;
&lt;br /&gt;
    var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
    title.className = &#039;clbi-left-title&#039;;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 박스 제목은 Lang.js의 현재 UI 언어를 따른다.&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
    var tocTitleText = (t &amp;amp;&amp;amp; t.toc) ? t.toc : &#039;목차&#039;;&lt;br /&gt;
&lt;br /&gt;
    title.textContent = tocTitleText;&lt;br /&gt;
&lt;br /&gt;
    var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
    body.className = &#039;clbi-left-content toc-sidebar-content&#039;;&lt;br /&gt;
&lt;br /&gt;
    var list = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    list.className = &#039;generated-toc&#039;;&lt;br /&gt;
&lt;br /&gt;
    headings.forEach(function (heading) {&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
        var level = heading.tagName.toLowerCase() === &#039;h3&#039; ? 3 : 2;&lt;br /&gt;
&lt;br /&gt;
        var item = document.createElement(&#039;li&#039;);&lt;br /&gt;
        item.className = &#039;toc-level-&#039; + level;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.setAttribute(&#039;href&#039;, &#039;#&#039; + id);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목 스크롤을 위해 텍스트를 별도 span으로 감싼다.&lt;br /&gt;
        var textWrap = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textWrap.className = &#039;toc-scroll-wrap&#039;;&lt;br /&gt;
&lt;br /&gt;
        var textSpan = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textSpan.className = &#039;toc-scroll-text&#039;;&lt;br /&gt;
        textSpan.textContent = text;&lt;br /&gt;
&lt;br /&gt;
        textWrap.appendChild(textSpan);&lt;br /&gt;
        link.appendChild(textWrap);&lt;br /&gt;
&lt;br /&gt;
        item.appendChild(link);&lt;br /&gt;
        list.appendChild(item);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    body.appendChild(list);&lt;br /&gt;
    tocBox.appendChild(title);&lt;br /&gt;
    tocBox.appendChild(body);&lt;br /&gt;
    leftSidebar.appendChild(tocBox);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: DOM 배치가 끝난 뒤 긴 제목 스크롤 여부를 계산한다.&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        initTocTitleScroll(tocBox);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            initTocTitleScroll(tocBox);&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 우측 광고판: 이미지/번역 캡션 목록&lt;br /&gt;
var RIGHT_BILLBOARD_ITEMS = [&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
        alt: &#039;PROOF TO THE WORLD / YOU ONCE PART OF IT&#039;,&lt;br /&gt;
        duration: 3000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;당신이 한때 이 세계의&#039;,&lt;br /&gt;
            &#039;&#039;,&lt;br /&gt;
            &#039;일부였다는 것을 증명하십시오&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-003.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    }&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItem(index) {&lt;br /&gt;
    var items = RIGHT_BILLBOARD_ITEMS;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        return {&lt;br /&gt;
            file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
            alt: &#039;&#039;,&lt;br /&gt;
            caption: []&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var normalized = index % items.length;&lt;br /&gt;
    if (normalized &amp;lt; 0) normalized += items.length;&lt;br /&gt;
&lt;br /&gt;
    return items[normalized];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardImageUrl(fileName) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/&#039; + encodeURIComponent(fileName || &#039;Side-visual-001.png&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardCaptionHtml(item) {&lt;br /&gt;
    var lines = item &amp;amp;&amp;amp; item.caption ? item.caption : [];&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    function escapeCaptionText(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/\&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    lines.forEach(function(line) {&lt;br /&gt;
        var text = String(line == null ? &#039;&#039; : line);&lt;br /&gt;
        var isGap = !text.trim();&lt;br /&gt;
        var className = &#039;right-billboard-caption-line&#039; + (isGap ? &#039; is-gap&#039; : &#039;&#039;);&lt;br /&gt;
        html += &#039;&amp;lt;span class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; + (isGap ? &#039;&amp;amp;nbsp;&#039; : escapeCaptionText(text)) + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setRightBillboardItem(index) {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    var item = getRightBillboardItem(index);&lt;br /&gt;
    var src = getRightBillboardImageUrl(item.file);&lt;br /&gt;
    var images = box.querySelectorAll(&#039;.right-billboard-image&#039;);&lt;br /&gt;
    var caption = box.querySelector(&#039;#right-billboard-caption&#039;);&lt;br /&gt;
    var emptySub = box.querySelector(&#039;.right-billboard-empty-sub&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, String(index));&lt;br /&gt;
    box.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(images, function(img) {&lt;br /&gt;
        img.style.display = &#039;&#039;;&lt;br /&gt;
        img.setAttribute(&#039;src&#039;, src);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, img.classList.contains(&#039;right-billboard-image-base&#039;) ? (item.alt || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (caption) {&lt;br /&gt;
        caption.innerHTML = getRightBillboardCaptionHtml(item);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (emptySub) {&lt;br /&gt;
        emptySub.textContent = item.file || &#039;Side-visual-001.png&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItemDuration(item) {&lt;br /&gt;
    var duration = item &amp;amp;&amp;amp; item.duration ? parseInt(item.duration, 10) : 3000;&lt;br /&gt;
&lt;br /&gt;
    if (Number.isNaN(duration) || duration &amp;lt; 500) {&lt;br /&gt;
        duration = 3000;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return duration;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initRightBillboardCarousel() {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-billboard-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    setRightBillboardItem(0);&lt;br /&gt;
&lt;br /&gt;
    if (!RIGHT_BILLBOARD_ITEMS || RIGHT_BILLBOARD_ITEMS.length &amp;lt;= 1) return;&lt;br /&gt;
&lt;br /&gt;
    function scheduleNext() {&lt;br /&gt;
        var current = parseInt(box.getAttribute(&#039;data-billboard-index&#039;) || &#039;0&#039;, 10);&lt;br /&gt;
        if (Number.isNaN(current)) current = 0;&lt;br /&gt;
&lt;br /&gt;
        var currentItem = getRightBillboardItem(current);&lt;br /&gt;
        var delay = getRightBillboardItemDuration(currentItem);&lt;br /&gt;
&lt;br /&gt;
        window.setTimeout(function() {&lt;br /&gt;
            if (!document.body.contains(box)) return;&lt;br /&gt;
&lt;br /&gt;
            if (!document.hidden) {&lt;br /&gt;
                setRightBillboardItem(current + 1);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            scheduleNext();&lt;br /&gt;
        }, delay);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scheduleNext();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function escapeRightBillboardAttr(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildRightBillboardBox() {&lt;br /&gt;
    var billboardInitial = getRightBillboardItem(0);&lt;br /&gt;
    var billboardSrc = getRightBillboardImageUrl(billboardInitial.file);&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box right-billboard-box left-billboard-box left-ad-box&amp;quot; data-billboard-index=&amp;quot;0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title right-billboard-title left-ad-title left-ad-title-iconless&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-ad&amp;quot; class=&amp;quot;left-ad-title-label&amp;quot;&amp;gt;Looking for a job?&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content left-ad-content-shell&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;right-billboard-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-recess&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;right-billboard-screen&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;right-billboard-image&amp;quot; class=&amp;quot;right-billboard-image right-billboard-image-base&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&#039; + escapeRightBillboardAttr(billboardInitial.alt || &#039;&#039;) + &#039;&amp;quot; onload=&amp;quot;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.remove(\&#039;is-empty\&#039;);}&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.add(\&#039;is-empty\&#039;);}&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-image-bloom&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-a&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-b&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-c&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-glitch&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-tear&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-empty&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-main&amp;quot;&amp;gt;SIGNAL EMPTY&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-sub&amp;quot;&amp;gt;&#039; + escapeRightBillboardAttr(billboardInitial.file || &#039;Side-visual-001.png&#039;) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;right-billboard-caption&amp;quot; class=&amp;quot;right-billboard-caption&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + getRightBillboardCaptionHtml(billboardInitial) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-bottom-finish left-ad-bottom-finish&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var GREAT_WALL_DATA_TITLE = &#039;프로젝트:The_Great_Wall/Data.json&#039;;&lt;br /&gt;
var GREAT_WALL_LIST_LIMIT = 0;&lt;br /&gt;
var greatWallState = {&lt;br /&gt;
    data: { entries: {} },&lt;br /&gt;
    loaded: false,&lt;br /&gt;
    loading: false,&lt;br /&gt;
    saving: false,&lt;br /&gt;
    selectedOwnEntry: false,&lt;br /&gt;
    statusText: &#039;&#039;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function normalizeGreatWallData(data) {&lt;br /&gt;
    var normalized = { entries: {} };&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    Object.keys(entries).forEach(function(key) {&lt;br /&gt;
        var item = entries[key];&lt;br /&gt;
        var user = item &amp;amp;&amp;amp; item.user ? String(item.user) : String(key || &#039;&#039;);&lt;br /&gt;
        var text = item &amp;amp;&amp;amp; item.text ? String(item.text) : &#039;&#039;;&lt;br /&gt;
        var timestamp = item &amp;amp;&amp;amp; item.timestamp ? String(item.timestamp) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!user || !text) return;&lt;br /&gt;
&lt;br /&gt;
        normalized.entries[user] = {&lt;br /&gt;
            user: user,&lt;br /&gt;
            text: text.slice(0, 140),&lt;br /&gt;
            timestamp: timestamp || new Date(0).toISOString()&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseGreatWallData(text) {&lt;br /&gt;
    var parsed;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;The Great Wall data parse failed:&#039;, err);&lt;br /&gt;
        parsed = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return normalizeGreatWallData(parsed);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function stringifyGreatWallData(data) {&lt;br /&gt;
    return JSON.stringify(normalizeGreatWallData(data), null, 2) + &#039;\n&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallRevisionText(page) {&lt;br /&gt;
    var rev;&lt;br /&gt;
    var slot;&lt;br /&gt;
&lt;br /&gt;
    if (!page || !page.revisions || !page.revisions.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    rev = page.revisions[0];&lt;br /&gt;
&lt;br /&gt;
    if (rev.slots &amp;amp;&amp;amp; rev.slots.main) {&lt;br /&gt;
        slot = rev.slots.main;&lt;br /&gt;
        return slot.content || slot[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return rev.content || rev[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchGreatWallData() {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
    return api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;revisions&#039;,&lt;br /&gt;
        titles: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
        rvprop: &#039;content|timestamp&#039;,&lt;br /&gt;
        rvslots: &#039;main&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages[0] || null;&lt;br /&gt;
&lt;br /&gt;
        if (!page || page.missing) {&lt;br /&gt;
            return { entries: {} };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parseGreatWallData(getGreatWallRevisionText(page));&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall load failed:&#039;, err);&lt;br /&gt;
        return { entries: {} };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallEntries(data) {&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    return Object.keys(entries).map(function(key) {&lt;br /&gt;
        return entries[key];&lt;br /&gt;
    }).filter(function(item) {&lt;br /&gt;
        return item &amp;amp;&amp;amp; item.user &amp;amp;&amp;amp; item.text;&lt;br /&gt;
    }).sort(function(a, b) {&lt;br /&gt;
        return String(a.timestamp || &#039;&#039;).localeCompare(String(b.timestamp || &#039;&#039;));&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallMessageTime(timestamp) {&lt;br /&gt;
    var date = timestamp ? new Date(timestamp) : null;&lt;br /&gt;
    var month;&lt;br /&gt;
    var day;&lt;br /&gt;
    var hour;&lt;br /&gt;
    var minute;&lt;br /&gt;
&lt;br /&gt;
    if (!date || isNaN(date.getTime())) return &#039;—&#039;;&lt;br /&gt;
&lt;br /&gt;
    month = String(date.getMonth() + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
    day = String(date.getDate()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    hour = String(date.getHours()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    minute = String(date.getMinutes()).padStart(2, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    return month + &#039;.&#039; + day + &#039; &#039; + hour + &#039;:&#039; + minute;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallAvatarSrc(user) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(String(user || &#039;&#039;)) + &#039;.png&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallEntryHtml(item, currentUser, selectedOwnEntry) {&lt;br /&gt;
    var isOwn = currentUser &amp;amp;&amp;amp; item.user === currentUser;&lt;br /&gt;
    var tag = isOwn ? &#039;button&#039; : &#039;div&#039;;&lt;br /&gt;
    var attrs = isOwn ? &#039; type=&amp;quot;button&amp;quot; data-great-wall-own-entry=&amp;quot;1&amp;quot; aria-label=&amp;quot;Edit your wall message&amp;quot;&#039; : &#039;&#039;;&lt;br /&gt;
    var className = &#039;great-wall-entry&#039; + (isOwn ? &#039; is-own&#039; : &#039;&#039;) + (isOwn &amp;amp;&amp;amp; selectedOwnEntry ? &#039; is-selected&#039; : &#039;&#039;);&lt;br /&gt;
    var avatarSrc = getGreatWallAvatarSrc(item.user);&lt;br /&gt;
    var messageTime = getGreatWallMessageTime(item.timestamp);&lt;br /&gt;
    var isoTime = item.timestamp || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;&#039; + tag + attrs + &#039; class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;img class=&amp;quot;great-wall-avatar&amp;quot; src=&amp;quot;&#039; + escapeClbiHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=&amp;amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;quot;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-bubble&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-entry-head&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-user&amp;quot;&amp;gt;@&#039; + escapeClbiHtml(item.user) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-time&amp;quot; title=&amp;quot;&#039; + escapeClbiHtml(isoTime) + &#039;&amp;quot;&amp;gt;&#039; + escapeClbiHtml(messageTime) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-text&amp;quot;&amp;gt;&#039; + escapeClbiHtml(item.text) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/&#039; + tag + &#039;&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var list = document.getElementById(&#039;great-wall-list&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
    var status = document.getElementById(&#039;great-wall-status&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var entries = getGreatWallEntries(greatWallState.data);&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var visibleEntries = [];&lt;br /&gt;
    var seen = {};&lt;br /&gt;
&lt;br /&gt;
    if (!box || !list) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.toggle(&#039;is-guest&#039;, !currentUser);&lt;br /&gt;
    box.classList.toggle(&#039;is-loading&#039;, !!greatWallState.loading);&lt;br /&gt;
    box.classList.toggle(&#039;is-saving&#039;, !!greatWallState.saving);&lt;br /&gt;
    box.classList.toggle(&#039;has-own-entry&#039;, !!ownEntry);&lt;br /&gt;
    box.classList.toggle(&#039;is-own-selected&#039;, !!greatWallState.selectedOwnEntry);&lt;br /&gt;
&lt;br /&gt;
    entries.forEach(function(item) {&lt;br /&gt;
        if (seen[item.user]) return;&lt;br /&gt;
        visibleEntries.push(item);&lt;br /&gt;
        seen[item.user] = true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.loading &amp;amp;&amp;amp; !greatWallState.loaded) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else if (!visibleEntries.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;NO MARKS&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else {&lt;br /&gt;
        list.innerHTML = visibleEntries.map(function(item) {&lt;br /&gt;
            return buildGreatWallEntryHtml(item, currentUser, greatWallState.selectedOwnEntry);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list &amp;amp;&amp;amp; list.scrollHeight &amp;gt; list.clientHeight) {&lt;br /&gt;
        list.scrollTop = list.scrollHeight;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (status) {&lt;br /&gt;
        // Status text is intentionally suppressed in the composer strip; the area is spacing-only UI.&lt;br /&gt;
        status.textContent = &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!input || !submit) return;&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        submit.disabled = false;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = &#039;Login required&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.saving || greatWallState.loading) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = greatWallState.saving ? &#039;SAVE&#039; : &#039;SYNC&#039;;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = greatWallState.saving ? &#039;Saving&#039; : &#039;Syncing&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.disabled = !ownEntry;&lt;br /&gt;
        deleteButton.title = ownEntry ? &#039;Delete your mark&#039; : &#039;No mark to delete&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        input.disabled = false;&lt;br /&gt;
        input.readOnly = true;&lt;br /&gt;
        input.setAttribute(&#039;aria-readonly&#039;, &#039;true&#039;);&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = &#039;UPDATE&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
    input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; greatWallState.selectedOwnEntry &amp;amp;&amp;amp; !input.value) {&lt;br /&gt;
        input.value = ownEntry.text || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    submit.disabled = false;&lt;br /&gt;
    submit.textContent = ownEntry ? &#039;UPDATE&#039; : &#039;POST&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function saveGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var text = input ? String(input.value || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!text) {&lt;br /&gt;
        greatWallState.statusText = &#039;EMPTY MARK&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (text.length &amp;gt; 140) {&lt;br /&gt;
        text = text.slice(0, 140);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SAVING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        data.entries[currentUser] = {&lt;br /&gt;
            user: currentUser,&lt;br /&gt;
            text: text,&lt;br /&gt;
            timestamp: new Date().toISOString()&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Update The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK UPDATED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall save failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;SAVE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function deleteGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!ownEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;NO MARK TO DELETE&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;DELETING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        if (data.entries &amp;amp;&amp;amp; data.entries[currentUser]) {&lt;br /&gt;
            delete data.entries[currentUser];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Delete The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK DELETED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall delete failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;DELETE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBoxWhenReady() {&lt;br /&gt;
    if (!document.getElementById(&#039;great-wall-sidebar&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    if (mw.Api) {&lt;br /&gt;
        initGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.loader &amp;amp;&amp;amp; mw.loader.using) {&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
            initGreatWallBox();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-great-wall-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-great-wall-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var ownButton = e.target.closest ? e.target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;]&#039;) : null;&lt;br /&gt;
        var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
        var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
&lt;br /&gt;
        if (!ownButton || !ownEntry) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = true;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
        if (input) {&lt;br /&gt;
            input.focus();&lt;br /&gt;
            input.setSelectionRange(input.value.length, input.value.length);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var target = e.target;&lt;br /&gt;
        var keepSelection;&lt;br /&gt;
&lt;br /&gt;
        if (!greatWallState.selectedOwnEntry || !target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
        keepSelection = target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;], .great-wall-editor, .great-wall-compose-sector&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (keepSelection) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        if (input) input.value = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (submit) {&lt;br /&gt;
        submit.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
                window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            saveGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            deleteGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (input) {&lt;br /&gt;
        input.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
            if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
                e.preventDefault();&lt;br /&gt;
                saveGreatWallEntry();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&#039;input&#039;, function() {&lt;br /&gt;
            if (input.value.length &amp;gt; 140) {&lt;br /&gt;
                input.value = input.value.slice(0, 140);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.loading = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SYNCING WALL&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        greatWallState.data = normalizeGreatWallData(data);&lt;br /&gt;
        greatWallState.loaded = true;&lt;br /&gt;
        greatWallState.loading = false;&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;great-wall-sidebar&amp;quot; class=&amp;quot;clbi-right-box great-wall-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title great-wall-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-great-wall&amp;quot;&amp;gt;The Great Wall&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content great-wall-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-list&amp;quot; class=&amp;quot;great-wall-list&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-compose-sector&amp;quot; aria-label=&amp;quot;The Great Wall editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;input id=&amp;quot;great-wall-input&amp;quot; class=&amp;quot;great-wall-input&amp;quot; type=&amp;quot;text&amp;quot; maxlength=&amp;quot;140&amp;quot; autocomplete=&amp;quot;off&amp;quot; placeholder=&amp;quot;담벼락&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-submit&amp;quot; class=&amp;quot;great-wall-submit&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;POST&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-delete&amp;quot; class=&amp;quot;great-wall-delete&amp;quot; type=&amp;quot;button&amp;quot; disabled&amp;gt;DEL&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-status&amp;quot; class=&amp;quot;great-wall-status&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeMainPortalGuestbookPreview() {&lt;br /&gt;
    var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    var guestbook = portal ? portal.querySelector(&#039;.guestbook-device&#039;) : null;&lt;br /&gt;
    var sideScreen;&lt;br /&gt;
    var grid;&lt;br /&gt;
&lt;br /&gt;
    if (!portal || !guestbook) return;&lt;br /&gt;
&lt;br /&gt;
    sideScreen = guestbook.closest ? guestbook.closest(&#039;.side-screen&#039;) : null;&lt;br /&gt;
    grid = sideScreen &amp;amp;&amp;amp; sideScreen.closest ? sideScreen.closest(&#039;.console-grid&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (guestbook.parentNode) {&lt;br /&gt;
        guestbook.parentNode.removeChild(guestbook);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sideScreen &amp;amp;&amp;amp; !(sideScreen.textContent || &#039;&#039;).trim() &amp;amp;&amp;amp; !sideScreen.querySelector(&#039;img,svg,video,canvas,form,input,button,a&#039;)) {&lt;br /&gt;
        if (sideScreen.parentNode) {&lt;br /&gt;
            sideScreen.parentNode.removeChild(sideScreen);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (grid) {&lt;br /&gt;
            grid.style.gridTemplateColumns = &#039;minmax(0,1fr)&#039;;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    portal.classList.add(&#039;is-great-wall-relocated&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildSiteInformationBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-right-box site-info-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title site-info-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span&amp;gt;정보&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content site-info-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;policy-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/개인정보처리방침&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;개인정보처리방침&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/면책_조항&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;면책 조항&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/라이선스&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;라이선스&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/크레딧&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;크레딧&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;social-strip&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://discord.gg/ctaeJ9d3Q5&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;DC&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.youtube.com/@nxdsxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;YT&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://x.com/nxd_sxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;X&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;WIP:&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 초기화 함수&lt;br /&gt;
function initSidebars() {&lt;br /&gt;
    var header = $(&#039;.liberty-content-header&#039;);&lt;br /&gt;
    var content = $(&#039;.liberty-content&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (header.length &amp;amp;&amp;amp; content.length) {&lt;br /&gt;
        header.prependTo(content);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-right-sidebar&#039;).length === 0) {&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        var isLoggedIn = username !== null;&lt;br /&gt;
        var avatarSrc = isLoggedIn&lt;br /&gt;
            ? &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + username + &#039;.png&#039;&lt;br /&gt;
            : &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&#039;;&lt;br /&gt;
&lt;br /&gt;
        var userBox;&lt;br /&gt;
&lt;br /&gt;
        if (isLoggedIn) {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a href=&amp;quot;/index.php/사용자:&#039; + username + &#039;&amp;quot; id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;&#039; + username + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-quick-actions&amp;quot; aria-label=&amp;quot;프로필 빠른 메뉴&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-inventory&amp;quot; aria-label=&amp;quot;인벤토리&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_PACKAGE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;인벤토리&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-achievements&amp;quot; aria-label=&amp;quot;업적&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_TROPHY + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;업적&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-notifications&amp;quot; aria-label=&amp;quot;알림&amp;quot;&amp;gt;&amp;lt;span id=&amp;quot;profile-quick-notification-icon&amp;quot; class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_BELL + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;알림&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:기여/&#039; + username + &#039;&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-contribution&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_TEXT + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;기여&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:주시문서목록&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-watchlist&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_EYE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;주시문서 목록&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:설정&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-preferences&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SETTINGS + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;설정&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그아웃&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn clbi-user-btn-logout&amp;quot; id=&amp;quot;clbi-btn-logout&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그아웃&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row profile-name-row-guest&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;Guest&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그인&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-login&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그인&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        var greatWallBox = &#039;&#039;;&lt;br /&gt;
        var siteInformationBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            greatWallBox = buildGreatWallBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;The Great Wall build failed:&#039;, err);&lt;br /&gt;
            greatWallBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            siteInformationBox = buildSiteInformationBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Site information build failed:&#039;, err);&lt;br /&gt;
            siteInformationBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        var sidebar = userBox + greatWallBox + siteInformationBox;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).append(&#039;&amp;lt;div id=&amp;quot;clbi-right-sidebar&amp;quot;&amp;gt;&#039; + sidebar + &#039;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        removeMainPortalGuestbookPreview();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    initGreatWallBoxWhenReady();&lt;br /&gt;
    removeMainPortalGuestbookPreview();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-left-sidebar&#039;).length === 0) {&lt;br /&gt;
var leftBillboardBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            leftBillboardBox = buildRightBillboardBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Left billboard build failed:&#039;, err);&lt;br /&gt;
            leftBillboardBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
var leftSidebar =&lt;br /&gt;
    &#039;&amp;lt;div id=&amp;quot;clbi-left-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-language&amp;quot;&amp;gt;언어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content sidebar-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selector&amp;quot; class=&amp;quot;sidebar-lang-selector sidebar-lang-dial&amp;quot; tabindex=&amp;quot;0&amp;quot; role=&amp;quot;group&amp;quot; aria-label=&amp;quot;언어 선택&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-dial-stage&amp;quot; class=&amp;quot;sidebar-lang-dial-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-fan&amp;quot; class=&amp;quot;sidebar-lang-fan&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selected-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-left&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-selected-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;한국어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-availability-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-right is-current&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-availability-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;CURRENT&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-sidebar-lang-apply&amp;quot; class=&amp;quot;sidebar-lang-apply&amp;quot; aria-label=&amp;quot;언어 적용&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;sidebar-lang-apply-mark&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;✓&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-news&amp;quot;&amp;gt;뉴스&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content clbi-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-title&amp;quot;&amp;gt;CHANGELOG&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-changelog-feed&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;a href=&amp;quot;/index.php/체인지로그&amp;quot; class=&amp;quot;news-post-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-post-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-post-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-main&amp;quot;&amp;gt;체인지로그&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-post-jump&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-divider&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-recent-title&amp;quot;&amp;gt;RECENT CHANGES&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-recent-feed&amp;quot; id=&amp;quot;clbi-left-recent-list&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;a class=&amp;quot;news-fill-image-slot&amp;quot; id=&amp;quot;clbi-left-news-fill-image&amp;quot; href=&amp;quot;/index.php/특수:최근바뀜&amp;quot; aria-label=&amp;quot;최근 바뀜으로 이동&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-fill-image-frame&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-fill-image&amp;quot; style=&amp;quot;--news-fill-image-url:url(\&#039;/index.php?title=특수:Redirect/file/Side-news-fill-001.png\&#039;);&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;img class=&amp;quot;news-fill-image-probe&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Side-news-fill-001.png&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.closest(\&#039;.news-fill-image-slot\&#039;).classList.add(\&#039;is-empty\&#039;);this.remove();&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        leftBillboardBox +&lt;br /&gt;
    &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).prepend(leftSidebar);&lt;br /&gt;
&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
        loadRecentChangesList(&#039;#clbi-left-recent-list&#039;, 10);&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
        updateLeftSidebarNationsImage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        initRightBillboardCarousel();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;Right billboard carousel failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
    applyMainPageStyle();&lt;br /&gt;
    initClbiCustomDocumentScrollbars();&lt;br /&gt;
    initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
        window.ProgressSystemWebUi.boot(&#039;initSidebars&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initNotifications();&lt;br /&gt;
            initProfile();&lt;br /&gt;
            moveTocToLeftSidebar();&lt;br /&gt;
        }, 300);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 800);&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 1500);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    loadLangScript(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initSidebars();&lt;br /&gt;
        }, 100);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click.profileQuickPlaceholder&#039;, &#039;#profile-quick-inventory, #profile-quick-achievements&#039;, function(e) {&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
function extractJsonArrayAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var depth = 0;&lt;br /&gt;
    var inString = false;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = String(text || &#039;&#039;).indexOf(&#039;[&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (inString) {&lt;br /&gt;
            if (escaped) {&lt;br /&gt;
                escaped = false;&lt;br /&gt;
            } else if (ch === &#039;\\&#039;) {&lt;br /&gt;
                escaped = true;&lt;br /&gt;
            } else if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
                inString = false;&lt;br /&gt;
            }&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            inString = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;[&#039;) depth += 1;&lt;br /&gt;
        if (ch === &#039;]&#039;) {&lt;br /&gt;
            depth -= 1;&lt;br /&gt;
            if (depth === 0) {&lt;br /&gt;
                try {&lt;br /&gt;
                    return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    return null;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function extractJsonStringAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var colon;&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    colon = text.indexOf(&#039;:&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (colon === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = text.indexOf(&#039;&amp;quot;&#039;, colon + 1);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start + 1; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (escaped) {&lt;br /&gt;
            escaped = false;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;\\&#039;) {&lt;br /&gt;
            escaped = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                return text.slice(start + 1, i);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function syncCatlinksConfigFromSpaDocument(doc) {&lt;br /&gt;
    var scripts = doc ? doc.querySelectorAll(&#039;script&#039;) : [];&lt;br /&gt;
    var categories = null;&lt;br /&gt;
    var hiddenCategories = null;&lt;br /&gt;
    var relevantPageName = null;&lt;br /&gt;
    var pageName = null;&lt;br /&gt;
    var i;&lt;br /&gt;
    var text;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; scripts.length; i += 1) {&lt;br /&gt;
        text = scripts[i].textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (categories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) categories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (hiddenCategories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgHiddenCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) hiddenCategories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (relevantPageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgRelevantPageName&#039;);&lt;br /&gt;
            if (value !== null) relevantPageName = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgPageName&#039;);&lt;br /&gt;
            if (value !== null) pageName = value;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.config.set(&#039;wgCategories&#039;, Array.isArray(categories) ? categories : []);&lt;br /&gt;
    mw.config.set(&#039;wgHiddenCategories&#039;, Array.isArray(hiddenCategories) ? hiddenCategories : []);&lt;br /&gt;
&lt;br /&gt;
    if (relevantPageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, relevantPageName);&lt;br /&gt;
    } else if (pageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    CLBI_CATLINKS_FETCH_TOKEN += 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// SPA 네비게이션&lt;br /&gt;
function shouldSkip(url) {&lt;br /&gt;
    return url.match(/action=edit|action=submit|action=history|action=delete|action=protect|action=purge|특수:로그인|특수:로그아웃|Special:UserLogin|Special:UserLogout|특수:사용자정보|특수:비밀번호바꾸기|uselang=/);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    if (window._spaInitialized) return;&lt;br /&gt;
    window._spaInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    function isInternal(url) {&lt;br /&gt;
        var a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = url;&lt;br /&gt;
        return a.hostname === window.location.hostname;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getCachedSpaPageHtml(url) {&lt;br /&gt;
        if (!window.EntryStore || typeof window.EntryStore.getTextSync !== &#039;function&#039;) return &#039;&#039;;&lt;br /&gt;
        return window.EntryStore.getTextSync(url) || window.EntryStore.getTextSync(String(url || &#039;&#039;).replace(/^https?:\/\/[^/]+/i, &#039;&#039;)) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchSpaPageHtml(url) {&lt;br /&gt;
        var cached = getCachedSpaPageHtml(url);&lt;br /&gt;
        if (cached) return Promise.resolve(cached);&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; }).then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDetachedEntryContent(newContent) {&lt;br /&gt;
        /*&lt;br /&gt;
        Initial boot prepares entry artifacts; SPA is only allowed to consume them.&lt;br /&gt;
        This hook runs while the fetched page is still detached, before the user sees it.&lt;br /&gt;
        It must stay synchronous or already-resolved: if a subsystem cannot prepare from&lt;br /&gt;
        EntryStore immediately, it should leave the old fallback path in place instead of&lt;br /&gt;
        opening BootGate during SPA.&lt;br /&gt;
        */&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.prepareContentForEntry === &#039;function&#039;) {&lt;br /&gt;
                window.NationsPanel.prepareContentForEntry(newContent);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;entry content preparation failed:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadPage(url) {&lt;br /&gt;
        invalidateProfileRender();&lt;br /&gt;
&lt;br /&gt;
        return fetchSpaPageHtml(url)&lt;br /&gt;
            .then(function(html) {&lt;br /&gt;
                var parser = new DOMParser();&lt;br /&gt;
                var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
                var scripts = doc.querySelectorAll(&#039;script&#039;);&lt;br /&gt;
                for (var i = 0; i &amp;lt; scripts.length; i++) {&lt;br /&gt;
                    var src = scripts[i].textContent;&lt;br /&gt;
&lt;br /&gt;
                    if (src.indexOf(&#039;wgNamespaceNumber&#039;) !== -1) {&lt;br /&gt;
                        var match = src.match(/&amp;quot;wgNamespaceNumber&amp;quot;:(-?\d+)/);&lt;br /&gt;
                        if (match) mw.config.set(&#039;wgNamespaceNumber&#039;, parseInt(match[1], 10));&lt;br /&gt;
&lt;br /&gt;
                        var matchTitle = src.match(/&amp;quot;wgTitle&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchTitle) mw.config.set(&#039;wgTitle&#039;, matchTitle[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchPage = src.match(/&amp;quot;wgPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchPage) mw.config.set(&#039;wgPageName&#039;, matchPage[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchArticle = src.match(/&amp;quot;wgArticleId&amp;quot;:(\d+)/);&lt;br /&gt;
                        if (matchArticle) {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, parseInt(matchArticle[1], 10));&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, 0);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchIsMainPage = src.match(/&amp;quot;wgIsMainPage&amp;quot;:(true|false)/);&lt;br /&gt;
                        if (matchIsMainPage) {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, matchIsMainPage[1] === &#039;true&#039;);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchSpecial = src.match(/&amp;quot;wgCanonicalSpecialPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchSpecial) {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, matchSpecial[1]);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                syncCatlinksConfigFromSpaDocument(doc);&lt;br /&gt;
&lt;br /&gt;
                var newContent = doc.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
                var newTitle = doc.querySelector(&#039;.mw-page-title-main&#039;);&lt;br /&gt;
                var newHead = doc.querySelector(&#039;title&#039;);&lt;br /&gt;
                var newHeader = doc.querySelector(&#039;.liberty-content-header&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (newContent) {&lt;br /&gt;
                    prepareDetachedEntryContent(newContent);&lt;br /&gt;
                    prepareSpaCatlinksBeforeInsert(newContent);&lt;br /&gt;
                    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
                    $(&#039;.liberty-content-main&#039;).html(newContent.innerHTML);&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    try {&lt;br /&gt;
                        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.renderPrepared === &#039;function&#039;) window.Decorations.renderPrepared();&lt;br /&gt;
                        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.renderPrepared === &#039;function&#039;) window.CLBI_DECORATIONS.renderPrepared();&lt;br /&gt;
                    } catch (err) {}&lt;br /&gt;
                    $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newTitle) {&lt;br /&gt;
                    $(&#039;.mw-page-title-main&#039;).html(newTitle.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHead) {&lt;br /&gt;
                    document.title = newHead.textContent;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHeader) {&lt;br /&gt;
                    $(&#039;.liberty-content-header&#039;).html(newHeader.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
                window.scrollTo(0, 0);&lt;br /&gt;
                mw.hook(&#039;wikipage.content&#039;).fire($(&#039;.liberty-content-main&#039;));&lt;br /&gt;
                applyMainPageStyle();&lt;br /&gt;
                initClbiCustomDocumentScrollbars();&lt;br /&gt;
                initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
                if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.handleSpaPageView === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.handleSpaPageView();&lt;br /&gt;
                } else if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.boot(&#039;spa&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 100);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 500);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 1200);&lt;br /&gt;
&lt;br /&gt;
                mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
                    initProfile();&lt;br /&gt;
                    moveTocToLeftSidebar();&lt;br /&gt;
                });&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function (err) {&lt;br /&gt;
                console.error(&#039;SPA page load failed:&#039;, err);&lt;br /&gt;
                $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
// 목차 링크는 전용 처리&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;#side-toc-box a, #toc a, .toc a&#039;, function(e) {&lt;br /&gt;
    var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
    if (!href || href.charAt(0) !== &#039;#&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var rawId = href.slice(1);&lt;br /&gt;
    if (!rawId) return;&lt;br /&gt;
&lt;br /&gt;
    var decodedId = rawId;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        decodedId = decodeURIComponent(rawId);&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        decodedId = rawId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var target = document.getElementById(decodedId);&lt;br /&gt;
&lt;br /&gt;
    if (!target &amp;amp;&amp;amp; window.CSS &amp;amp;&amp;amp; CSS.escape) {&lt;br /&gt;
        target = document.querySelector(&#039;#&#039; + CSS.escape(decodedId));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!target) return;&lt;br /&gt;
&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
    var scrollTarget = target.closest(&#039;h2, h3&#039;) || target;&lt;br /&gt;
&lt;br /&gt;
    scrollTarget.scrollIntoView({&lt;br /&gt;
        behavior: &#039;auto&#039;,&lt;br /&gt;
        block: &#039;start&#039;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    history.replaceState(null, &#039;&#039;, &#039;#&#039; + rawId);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
    /* 길라잡이는 문서 링크가 아니라 대문 본문 화면 탭이다. */&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;, function(e) {&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.toggle === &#039;function&#039;) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopImmediatePropagation();&lt;br /&gt;
            window.BottomGuideNav.toggle();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;a&#039;, function(e) {&lt;br /&gt;
        // 휠 클릭, 새 탭 열기, 보조키 이동은 브라우저 기본 동작을 유지한다.&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        /* 길라잡이 화면 탭은 위 전용 처리 외에는 SPA 문서 이동 대상으로 삼지 않는다. */&lt;br /&gt;
        if ($(this).is(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
        if (!href) return;&lt;br /&gt;
&lt;br /&gt;
        // 목차 링크는 별도 핸들러에서 처리&lt;br /&gt;
        if ($(this).closest(&#039;#side-toc-box, #toc, .toc&#039;).length) return;&lt;br /&gt;
&lt;br /&gt;
        // 단순 해시 링크는 SPA 가로채기 제외&lt;br /&gt;
        if (href.startsWith(&#039;#&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.href = href;&lt;br /&gt;
&lt;br /&gt;
        var samePath = decodeURIComponent(link.pathname) === decodeURIComponent(window.location.pathname);&lt;br /&gt;
        var sameSearch = (link.search || &#039;&#039;) === (window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; samePath &amp;amp;&amp;amp; sameSearch) return;&lt;br /&gt;
&lt;br /&gt;
        var currentBase = window.location.href.split(&#039;#&#039;)[0];&lt;br /&gt;
        var targetBase = link.href.split(&#039;#&#039;)[0];&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; currentBase === targetBase) return;&lt;br /&gt;
&lt;br /&gt;
        if (!isInternal(href)) return;&lt;br /&gt;
        if (shouldSkip(href)) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        playStaticSound();&lt;br /&gt;
        /*&lt;br /&gt;
        SPA must remain a consumer phase.  If the target page HTML was prepared by&lt;br /&gt;
        the first-load boot pack, do not show the legacy page-loading veil.  The&lt;br /&gt;
        route will consume cached HTML and detached entry artifacts before insertion.&lt;br /&gt;
        */&lt;br /&gt;
        if (getCachedSpaPageHtml(href)) {&lt;br /&gt;
            $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;body&#039;).addClass(&#039;page-loading&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        history.pushState(null, &#039;&#039;, href);&lt;br /&gt;
        loadPage(href);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;popstate&#039;, function() {&lt;br /&gt;
        loadPage(window.location.href);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ========== CLBI Custom Document Scrollbar ========== */&lt;br /&gt;
function isGeneralDocumentView() {&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    if (!body) return false;&lt;br /&gt;
&lt;br /&gt;
    return body.classList.contains(&#039;action-view&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-main-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-system-doc-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;backend-system-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiDocumentScrollTargets() {&lt;br /&gt;
    if (!isGeneralDocumentView()) return [];&lt;br /&gt;
&lt;br /&gt;
    return Array.prototype.slice.call(document.querySelectorAll(&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; #mw-content-text .mw-parser-output, &#039; +&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; .mw-body-content .mw-parser-output&#039;&lt;br /&gt;
    )).filter(function (el, index, list) {&lt;br /&gt;
        return el &amp;amp;&amp;amp; list.indexOf(el) === index;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiOuterWellForScroll(scrollEl) {&lt;br /&gt;
    var main = scrollEl ? scrollEl.closest(&#039;.liberty-content-main&#039;) : null;&lt;br /&gt;
    var children;&lt;br /&gt;
    var i;&lt;br /&gt;
    var child;&lt;br /&gt;
&lt;br /&gt;
    if (!main) return null;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(main.children || []);&lt;br /&gt;
    for (i = 0; i &amp;lt; children.length; i += 1) {&lt;br /&gt;
        child = children[i];&lt;br /&gt;
        if (&lt;br /&gt;
            child &amp;amp;&amp;amp;&lt;br /&gt;
            (child.id === &#039;mw-content-text&#039; || child.classList.contains(&#039;mw-body-content&#039;)) &amp;amp;&amp;amp;&lt;br /&gt;
            child.contains(scrollEl)&lt;br /&gt;
        ) {&lt;br /&gt;
            return child;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return scrollEl.parentElement || null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiCustomScrollbar(well, scrollEl) {&lt;br /&gt;
    var bar = well.querySelector(&#039;:scope &amp;gt; .clbi-custom-scrollbar&#039;);&lt;br /&gt;
    var up;&lt;br /&gt;
    var track;&lt;br /&gt;
    var thumb;&lt;br /&gt;
    var down;&lt;br /&gt;
&lt;br /&gt;
    if (!bar) {&lt;br /&gt;
        bar = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bar.className = &#039;clbi-custom-scrollbar&#039;;&lt;br /&gt;
        bar.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        bar.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-up&amp;quot; data-scroll-arrow=&amp;quot;up&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-track&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;clbi-custom-scroll-thumb&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-down&amp;quot; data-scroll-arrow=&amp;quot;down&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        well.appendChild(bar);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.__clbiScrollTarget = scrollEl;&lt;br /&gt;
    up = bar.querySelector(&#039;.clbi-custom-scroll-arrow-up&#039;);&lt;br /&gt;
    track = bar.querySelector(&#039;.clbi-custom-scroll-track&#039;);&lt;br /&gt;
    thumb = bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;);&lt;br /&gt;
    down = bar.querySelector(&#039;.clbi-custom-scroll-arrow-down&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (up &amp;amp;&amp;amp; !up.__clbiBound) {&lt;br /&gt;
        up.__clbiBound = true;&lt;br /&gt;
        up.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop -= 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (down &amp;amp;&amp;amp; !down.__clbiBound) {&lt;br /&gt;
        down.__clbiBound = true;&lt;br /&gt;
        down.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop += 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (track &amp;amp;&amp;amp; !track.__clbiBound) {&lt;br /&gt;
        track.__clbiBound = true;&lt;br /&gt;
        track.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var rect;&lt;br /&gt;
            var thumbRect;&lt;br /&gt;
            var target;&lt;br /&gt;
            var direction;&lt;br /&gt;
&lt;br /&gt;
            if (e.target === thumb) return;&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            target = bar.__clbiScrollTarget;&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            rect = track.getBoundingClientRect();&lt;br /&gt;
            thumbRect = thumb.getBoundingClientRect();&lt;br /&gt;
            direction = e.clientY &amp;lt; thumbRect.top ? -1 : 1;&lt;br /&gt;
            target.scrollTop += direction * Math.max(60, Math.floor(target.clientHeight * 0.82));&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (thumb &amp;amp;&amp;amp; !thumb.__clbiBound) {&lt;br /&gt;
        thumb.__clbiBound = true;&lt;br /&gt;
        thumb.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var target = bar.__clbiScrollTarget;&lt;br /&gt;
            var startY;&lt;br /&gt;
            var startScroll;&lt;br /&gt;
            var maxScroll;&lt;br /&gt;
            var maxThumbTop;&lt;br /&gt;
            var trackHeight;&lt;br /&gt;
            var thumbHeight;&lt;br /&gt;
&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            startY = e.clientY;&lt;br /&gt;
            startScroll = target.scrollTop;&lt;br /&gt;
            maxScroll = Math.max(1, target.scrollHeight - target.clientHeight);&lt;br /&gt;
            trackHeight = track ? track.clientHeight : 0;&lt;br /&gt;
            thumbHeight = thumb.offsetHeight || 0;&lt;br /&gt;
            maxThumbTop = Math.max(1, trackHeight - thumbHeight);&lt;br /&gt;
&lt;br /&gt;
            bar.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
            function onMove(moveEvent) {&lt;br /&gt;
                var dy = moveEvent.clientY - startY;&lt;br /&gt;
                target.scrollTop = startScroll + (dy / maxThumbTop) * maxScroll;&lt;br /&gt;
                updateClbiCustomScrollbar(bar);&lt;br /&gt;
                moveEvent.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function onUp() {&lt;br /&gt;
                bar.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                document.removeEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
                document.removeEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            document.addEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
            document.addEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!scrollEl.__clbiCustomScrollbarBound) {&lt;br /&gt;
        scrollEl.__clbiCustomScrollbarBound = true;&lt;br /&gt;
        scrollEl.addEventListener(&#039;scroll&#039;, function () {&lt;br /&gt;
            if (scrollEl.__clbiCustomScrollbar) {&lt;br /&gt;
                updateClbiCustomScrollbar(scrollEl.__clbiCustomScrollbar);&lt;br /&gt;
            }&lt;br /&gt;
        }, { passive: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scrollEl.__clbiCustomScrollbar = bar;&lt;br /&gt;
    updateClbiCustomScrollbar(bar);&lt;br /&gt;
&lt;br /&gt;
    return bar;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiCustomScrollbar(bar) {&lt;br /&gt;
    var scrollEl = bar &amp;amp;&amp;amp; bar.__clbiScrollTarget;&lt;br /&gt;
    var track = bar ? bar.querySelector(&#039;.clbi-custom-scroll-track&#039;) : null;&lt;br /&gt;
    var thumb = bar ? bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;) : null;&lt;br /&gt;
    var maxScroll;&lt;br /&gt;
    var trackHeight;&lt;br /&gt;
    var thumbHeight;&lt;br /&gt;
    var maxTop;&lt;br /&gt;
    var top;&lt;br /&gt;
&lt;br /&gt;
    if (!bar || !scrollEl || !track || !thumb) return;&lt;br /&gt;
&lt;br /&gt;
    maxScroll = scrollEl.scrollHeight - scrollEl.clientHeight;&lt;br /&gt;
    if (maxScroll &amp;lt;= 1) {&lt;br /&gt;
        bar.classList.add(&#039;is-hidden&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.classList.remove(&#039;is-hidden&#039;);&lt;br /&gt;
&lt;br /&gt;
    trackHeight = Math.max(1, track.clientHeight || 1);&lt;br /&gt;
    thumbHeight = Math.max(12, Math.floor((scrollEl.clientHeight / Math.max(scrollEl.scrollHeight, 1)) * trackHeight));&lt;br /&gt;
    thumbHeight = Math.min(trackHeight, thumbHeight);&lt;br /&gt;
    maxTop = Math.max(0, trackHeight - thumbHeight);&lt;br /&gt;
    top = maxScroll &amp;gt; 0 ? Math.round((scrollEl.scrollTop / maxScroll) * maxTop) : 0;&lt;br /&gt;
&lt;br /&gt;
    thumb.style.height = thumbHeight + &#039;px&#039;;&lt;br /&gt;
    thumb.style.transform = &#039;translateY(&#039; + top + &#039;px)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiCustomDocumentScrollbars() {&lt;br /&gt;
    var existing = Array.prototype.slice.call(document.querySelectorAll(&#039;.clbi-custom-scrollbar&#039;));&lt;br /&gt;
    var targets = getClbiDocumentScrollTargets();&lt;br /&gt;
    var liveBars = [];&lt;br /&gt;
&lt;br /&gt;
    if (!targets.length) {&lt;br /&gt;
        existing.forEach(function (bar) { bar.remove(); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    targets.forEach(function (scrollEl) {&lt;br /&gt;
        var well = getClbiOuterWellForScroll(scrollEl);&lt;br /&gt;
        var bar;&lt;br /&gt;
&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
        bar = buildClbiCustomScrollbar(well, scrollEl);&lt;br /&gt;
        liveBars.push(bar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    existing.forEach(function (bar) {&lt;br /&gt;
        if (liveBars.indexOf(bar) === -1) bar.remove();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (!window.__clbiCustomScrollbarResizeBound) {&lt;br /&gt;
    window.__clbiCustomScrollbarResizeBound = true;&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        setTimeout(initClbiCustomDocumentScrollbars, 60);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시간 계산 함수&lt;br /&gt;
function timeAgo(timestamp) {&lt;br /&gt;
    var now = new Date();&lt;br /&gt;
    var date = new Date(timestamp);&lt;br /&gt;
    var diff = Math.floor((now - date) / 1000);&lt;br /&gt;
&lt;br /&gt;
    if (diff &amp;lt; 60) return diff + &#039;초 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 3600) return Math.floor(diff / 60) + &#039;분 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 86400) return Math.floor(diff / 3600) + &#039;시간 전&#039;;&lt;br /&gt;
    return Math.floor(diff / 86400) + &#039;일 전&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
function getFoldTexts() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return (window.LANG &amp;amp;&amp;amp; window.LANG[lang])&lt;br /&gt;
        ? window.LANG[lang]&lt;br /&gt;
        : (window.LANG ? window.LANG.ko : { expand: &#039;펼치기&#039;, collapse: &#039;접기&#039; });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function refreshOpenAncestors($start) {&lt;br /&gt;
    $start.parents(&#039;[id^=&amp;quot;collapsible&amp;quot;]&#039;).each(function () {&lt;br /&gt;
        var $parent = $(this);&lt;br /&gt;
        if (!$parent.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        // 이미 fully open 상태면 굳이 다시 잠그지 않음&lt;br /&gt;
        if ($parent.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $parent.css(&#039;max-height&#039;, this.scrollHeight + &#039;px&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindInnerResizeUpdates($target) {&lt;br /&gt;
    // 이미지 늦게 로드될 때 높이 갱신&lt;br /&gt;
    $target.find(&#039;img&#039;).off(&#039;.foldimg&#039;).on(&#039;load.foldimg&#039;, function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            if ($target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
                $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function openFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;opening&#039;);&lt;br /&gt;
    $target.addClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 열린 뒤 자연 확장 가능하게 만들기 위해 먼저 px로 열기&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.collapse);&lt;br /&gt;
&lt;br /&gt;
    bindInnerResizeUpdates($target);&lt;br /&gt;
&lt;br /&gt;
    // 바깥 펼접 즉시 갱신&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    // 전환 끝나면 none으로 풀어서 중첩 펼접/동적 내용 증가를 자연스럽게 허용&lt;br /&gt;
    $target.off(&#039;transitionend.foldopen&#039;).on(&#039;transitionend.foldopen&#039;, function (e) {&lt;br /&gt;
        if (e.target !== this) return;&lt;br /&gt;
        if (!$target.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        $target.css(&#039;max-height&#039;, &#039;none&#039;);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;open&#039;);&lt;br /&gt;
&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // 늦게 렌더되는 콘텐츠 대응&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 80);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 220);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function closeFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    // none 상태에서 닫으면 transition이 안 되므로 실제 높이로 고정&lt;br /&gt;
    if ($target.css(&#039;max-height&#039;) === &#039;none&#039; || $target.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;closing&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target.removeClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.expand);&lt;br /&gt;
&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;closed&#039;);&lt;br /&gt;
    }, 250);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function () {&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiToggle&#039;, &#039;.toggleBtn&#039;, function () {&lt;br /&gt;
            var $btn = $(this);&lt;br /&gt;
            var targetId = $btn.data(&#039;target&#039;);&lt;br /&gt;
            var $target = $(&#039;#&#039; + targetId);&lt;br /&gt;
            if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
            var scrollY = window.scrollY;&lt;br /&gt;
&lt;br /&gt;
            if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
                closeFold($target, $btn);&lt;br /&gt;
            } else {&lt;br /&gt;
                openFold($target, $btn);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            window.scrollTo(0, scrollY);&lt;br /&gt;
        });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// ========== 프로필 시스템 ==========&lt;br /&gt;
function initProfile() {&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var token = ++PROFILE_RENDER_TOKEN;&lt;br /&gt;
    var ns = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var title = mw.config.get(&#039;wgTitle&#039;);&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    var isProfileSettings = specialPage === &#039;사용자정보&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, ns === 2);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-settings-page&#039;, isProfileSettings);&lt;br /&gt;
&lt;br /&gt;
    if (ns === 2) {&lt;br /&gt;
        var profileUser = title.split(&#039;/&#039;)[0];&lt;br /&gt;
        renderProfile(profileUser, token);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isProfileSettings) {&lt;br /&gt;
        initUserProfilePage();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderProfile(username, token) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        list: &#039;users&#039;,&lt;br /&gt;
        ususers: username,&lt;br /&gt;
        usprop: &#039;editcount&#039;&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        if (token !== PROFILE_RENDER_TOKEN) return;&lt;br /&gt;
        if (mw.config.get(&#039;wgNamespaceNumber&#039;) !== 2) return;&lt;br /&gt;
&lt;br /&gt;
        var currentTitle = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).split(&#039;/&#039;)[0];&lt;br /&gt;
        if (currentTitle !== username) return;&lt;br /&gt;
&lt;br /&gt;
        var user = data.query.users[0];&lt;br /&gt;
        var contentEl = document.getElementById(&#039;mw-content-text&#039;);&lt;br /&gt;
        if (!contentEl) return;&lt;br /&gt;
&lt;br /&gt;
        var pageContent = contentEl.querySelector(&#039;.mw-parser-output&#039;) || contentEl;&lt;br /&gt;
        injectProfileCard(username, user, pageContent);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function injectProfileCard(username, userData, container) {&lt;br /&gt;
    var isOwnPage = mw.config.get(&#039;wgUserName&#039;) === username;&lt;br /&gt;
    var editCount = (userData &amp;amp;&amp;amp; userData.editcount) ? userData.editcount : 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.classList.add(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var safeUsername = escapeHtml(username);&lt;br /&gt;
    var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(username) + &#039;.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var fallbackSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var editBtn = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;a href=&amp;quot;/index.php/특수:사용자정보&amp;quot; class=&amp;quot;profile-edit-btn&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-label&amp;quot;&amp;gt;프로필 수정&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-arrow&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var progressHtml = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;div class=&amp;quot;profile-page-progress is-syncing&amp;quot; data-profile-progress&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;LEVEL RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-page-progress-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-level&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;profile-page-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-sub&amp;quot;&amp;gt;SYNCING&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-meta&amp;quot;&amp;gt;TODAY — · DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var card = document.createElement(&#039;div&#039;);&lt;br /&gt;
    card.className = &#039;profile-card profile-page-console&#039;;&lt;br /&gt;
    card.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-titlebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;USER PROFILE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;OFFICIAL ARCHIVE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-identity-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-avatar-bay&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;&#039; + fallbackSrc + &#039;\&#039;;&amp;quot; alt=&amp;quot;&#039; + safeUsername + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-info-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-nameplate&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;h2 class=&amp;quot;profile-username&amp;quot;&amp;gt;&#039; + safeUsername + &#039;&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
                        editBtn +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-name&amp;quot; data-field=&amp;quot;name&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-role&amp;quot; data-field=&amp;quot;role&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-discord&amp;quot; data-field=&amp;quot;discord&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-lower-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-bio-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;BIOGRAPHY&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-bio&amp;quot; data-field=&amp;quot;bio&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-stats-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-stats&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        progressHtml +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-stat-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot;&amp;gt;&#039; + editCount + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;수정 횟수&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot; data-contrib-pages-stat&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot; data-contrib-pages-value&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;기여 문서&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-info-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;TIME&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-time&amp;gt;UTC --:--&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;LANGUAGE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-language&amp;gt;—&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    container.insertBefore(card, container.firstChild);&lt;br /&gt;
    loadProfileFields(username, card);&lt;br /&gt;
    loadProfileContributionPages(username, card);&lt;br /&gt;
    updateProfilePageEnvironment(card, null);&lt;br /&gt;
&lt;br /&gt;
    if (isOwnPage) {&lt;br /&gt;
        loadProfileProgressForUserPage(card);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getProfileLanguageLabel() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || (lang ? lang.toUpperCase() : &#039;—&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfilePageEnvironment(card, summary) {&lt;br /&gt;
    if (!card) return;&lt;br /&gt;
&lt;br /&gt;
    var timezone = summary &amp;amp;&amp;amp; summary.timezone ? summary.timezone : &#039;UTC&#039;;&lt;br /&gt;
    var timeEl = card.querySelector(&#039;[data-profile-time]&#039;);&lt;br /&gt;
    var langEl = card.querySelector(&#039;[data-profile-language]&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (timeEl) {&lt;br /&gt;
        try {&lt;br /&gt;
            timeEl.textContent = timezone + &#039; &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: timezone&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            timeEl.textContent = &#039;UTC &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: &#039;UTC&#039;&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (langEl) {&lt;br /&gt;
        langEl.textContent = getProfileLanguageLabel();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileContributionPages(username, card) {&lt;br /&gt;
    if (!username || !card) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var valueEl = card.querySelector(&#039;[data-contrib-pages-value]&#039;);&lt;br /&gt;
    if (valueEl) valueEl.textContent = &#039;SYNC&#039;;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        var pages = Object.create(null);&lt;br /&gt;
        var cont = {};&lt;br /&gt;
        var guard = 0;&lt;br /&gt;
&lt;br /&gt;
        function requestNext() {&lt;br /&gt;
            guard++;&lt;br /&gt;
&lt;br /&gt;
            var params = Object.assign({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                list: &#039;usercontribs&#039;,&lt;br /&gt;
                ucuser: username,&lt;br /&gt;
                ucnamespace: 0,&lt;br /&gt;
                ucprop: &#039;title&#039;,&lt;br /&gt;
                uclimit: &#039;max&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }, cont);&lt;br /&gt;
&lt;br /&gt;
            return api.get(params).then(function (data) {&lt;br /&gt;
                var rows = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.usercontribs ? data.query.usercontribs : [];&lt;br /&gt;
&lt;br /&gt;
                rows.forEach(function (row) {&lt;br /&gt;
                    if (row &amp;amp;&amp;amp; row.title) pages[row.title] = true;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                if (data &amp;amp;&amp;amp; data.continue &amp;amp;&amp;amp; data.continue.uccontinue &amp;amp;&amp;amp; guard &amp;lt; 40) {&lt;br /&gt;
                    cont = data.continue;&lt;br /&gt;
                    return requestNext();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (valueEl) valueEl.textContent = Object.keys(pages).length;&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestNext().fail(function () {&lt;br /&gt;
            if (valueEl) valueEl.textContent = &#039;—&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileProgressForUserPage(card) {&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;progress_summary&#039;,&lt;br /&gt;
            format: &#039;json&#039;,&lt;br /&gt;
            formatversion: 2&lt;br /&gt;
        }).then(function (data) {&lt;br /&gt;
            var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
            if (!payload || !payload.available || !payload.summary) return;&lt;br /&gt;
            updateUserPageProgress(card, payload.summary);&lt;br /&gt;
            updateProfilePageEnvironment(card, payload.summary);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateUserPageProgress(card, summary) {&lt;br /&gt;
    var panel = card.querySelector(&#039;[data-profile-progress]&#039;);&lt;br /&gt;
    if (!panel || !summary) return;&lt;br /&gt;
&lt;br /&gt;
    var level = summary.level || 1;&lt;br /&gt;
    var totalXp = summary.totalXp || 0;&lt;br /&gt;
    var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
    var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
    var percent = Math.max(0, Math.min(100, summary.progressPercent || 0));&lt;br /&gt;
    var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
    var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
    var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
&lt;br /&gt;
    panel.classList.remove(&#039;is-syncing&#039;);&lt;br /&gt;
    panel.classList.toggle(&#039;is-max-level&#039;, isMaxLevel);&lt;br /&gt;
&lt;br /&gt;
    var levelEl = panel.querySelector(&#039;.profile-page-level&#039;);&lt;br /&gt;
    var totalEl = panel.querySelector(&#039;.profile-page-total-xp&#039;);&lt;br /&gt;
    var fillEl = panel.querySelector(&#039;.profile-page-xp-fill&#039;);&lt;br /&gt;
    var subEl = panel.querySelector(&#039;.profile-page-progress-sub&#039;);&lt;br /&gt;
    var metaEl = panel.querySelector(&#039;.profile-page-progress-meta&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (levelEl) levelEl.textContent = (isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level;&lt;br /&gt;
    if (totalEl) totalEl.textContent = totalXp + &#039; XP&#039;;&lt;br /&gt;
    if (fillEl) fillEl.style.width = percent + &#039;%&#039;;&lt;br /&gt;
    if (subEl) subEl.textContent = isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;);&lt;br /&gt;
    if (metaEl) metaEl.textContent = &#039;TODAY &#039; + dailyXp + &#039; XP · DISCOVERED &#039; + discoveries;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileFields(username, card) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;userprofile&#039;,&lt;br /&gt;
        user: username&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var profile = data.userprofile;&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: profile.name || &#039;&#039;,&lt;br /&gt;
            discord: profile.discord || &#039;&#039;,&lt;br /&gt;
            role: profile.role || &#039;&#039;,&lt;br /&gt;
            bio: profile.bio || &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    }).fail(function() {&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: &#039;&#039;,&lt;br /&gt;
            discord: &#039;&#039;,&lt;br /&gt;
            role: &#039;&#039;,&lt;br /&gt;
            bio: &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfileFields(card, data) {&lt;br /&gt;
    var nameEl = card.querySelector(&#039;[data-field=&amp;quot;name&amp;quot;]&#039;);&lt;br /&gt;
    var roleEl = card.querySelector(&#039;[data-field=&amp;quot;role&amp;quot;]&#039;);&lt;br /&gt;
    var discordEl = card.querySelector(&#039;[data-field=&amp;quot;discord&amp;quot;]&#039;);&lt;br /&gt;
    var bioEl = card.querySelector(&#039;[data-field=&amp;quot;bio&amp;quot;]&#039;);&lt;br /&gt;
    if (nameEl) nameEl.textContent = data.name || &#039;&#039;;&lt;br /&gt;
    if (roleEl) roleEl.textContent = data.role || &#039;&#039;;&lt;br /&gt;
    if (discordEl) discordEl.textContent = data.discord ? (&#039;디스코드: &#039; + data.discord) : &#039;&#039;;&lt;br /&gt;
    if (bioEl) bioEl.textContent = data.bio || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
// ========== 프로필 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
// ========== 알림 시스템 ==========&lt;br /&gt;
function ensureNotificationPopup() {&lt;br /&gt;
    if (document.getElementById(&#039;clbi-notification-popup&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    var popup = document.createElement(&#039;div&#039;);&lt;br /&gt;
    popup.id = &#039;clbi-notification-popup&#039;;&lt;br /&gt;
    popup.style.cssText =&lt;br /&gt;
        &#039;display:none;position:fixed;z-index:99999;width:320px;max-height:420px;&#039; +&lt;br /&gt;
        &#039;background:#0a0909;border:2px solid #854369;border-radius:5px;&#039; +&lt;br /&gt;
        &#039;box-shadow:0 0 0 1px #1a1a1a, 0 8px 24px rgba(0,0,0,0.55);overflow:hidden;&#039;;&lt;br /&gt;
&lt;br /&gt;
    popup.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:10px 12px;border-bottom:2px solid #854369;background:linear-gradient(to bottom, #171114 0%, #0a0909 100%);color:#E2E2E2;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:space-between;gap:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;알림&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-notification-readall&amp;quot; style=&amp;quot;background:#171717;border:1px solid #854369;border-radius:6px;color:#E2E2E2;font-size:11px;font-weight:700;padding:4px 8px;cursor:pointer;&amp;quot;&amp;gt;전체 읽음&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;clbi-notification-list&amp;quot; style=&amp;quot;max-height:320px;overflow-y:auto;padding:8px 0;color:#E2E2E2;font-size:12px;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:8px;border-top:1px solid #2a2a2a;background:#111;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;/index.php?title=Special:Notifications&amp;quot; id=&amp;quot;clbi-notification-more&amp;quot; style=&amp;quot;display:block;width:100%;text-align:center;padding:8px 10px;border-radius:6px;background:#171717;border:1px solid #854369;color:#E2E2E2 !important;text-decoration:none !important;font-size:12px;font-weight:700;&amp;quot;&amp;gt;더보기&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    document.body.appendChild(popup);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function positionNotificationPopup() {&lt;br /&gt;
    var btn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
    var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
    if (!btn || !popup) return;&lt;br /&gt;
&lt;br /&gt;
    var rect = btn.getBoundingClientRect();&lt;br /&gt;
    var top = rect.bottom + 6;&lt;br /&gt;
    var left = rect.left + (rect.width / 2) - (popup.offsetWidth / 2);&lt;br /&gt;
&lt;br /&gt;
    if (left &amp;lt; 8) left = 8;&lt;br /&gt;
    if (left + popup.offsetWidth &amp;gt; window.innerWidth - 8) {&lt;br /&gt;
        left = window.innerWidth - popup.offsetWidth - 8;&lt;br /&gt;
    }&lt;br /&gt;
    if (top + popup.offsetHeight &amp;gt; window.innerHeight - 8) {&lt;br /&gt;
        top = Math.max(8, rect.top - popup.offsetHeight - 6);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    popup.style.top = top + &#039;px&#039;;&lt;br /&gt;
    popup.style.left = left + &#039;px&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseNotificationItemsFromHtml(html) {&lt;br /&gt;
    var parser = new DOMParser();&lt;br /&gt;
    var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.mw-echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;.mw-echo-ui-notificationsInboxWidgetRow&#039;,&lt;br /&gt;
        &#039;.echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;li[data-notification-id]&#039;,&lt;br /&gt;
        &#039;.mw-echo-notifications-list li&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var items = [];&lt;br /&gt;
    for (var i = 0; i &amp;lt; selectors.length; i++) {&lt;br /&gt;
        items = Array.prototype.slice.call(doc.querySelectorAll(selectors[i]));&lt;br /&gt;
        if (items.length) break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return items.slice(0, 5).map(function(item) {&lt;br /&gt;
        var link = item.querySelector(&#039;a[href]&#039;);&lt;br /&gt;
        var href = link ? link.getAttribute(&#039;href&#039;) : &#039;/index.php?title=Special:Notifications&#039;;&lt;br /&gt;
        var text = (item.textContent || &#039;&#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        var notificationId =&lt;br /&gt;
            item.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
            &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!notificationId) {&lt;br /&gt;
            var anyWithId = item.querySelector(&#039;[data-notification-id], [data-id], [data-notification]&#039;);&lt;br /&gt;
            if (anyWithId) {&lt;br /&gt;
                notificationId =&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (href &amp;amp;&amp;amp; href.indexOf(&#039;http&#039;) !== 0) {&lt;br /&gt;
            href = href.charAt(0) === &#039;/&#039;&lt;br /&gt;
                ? href&lt;br /&gt;
                : &#039;/index.php&#039; + (href.charAt(0) === &#039;?&#039; ? href : &#039;/&#039; + href);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            id: notificationId,&lt;br /&gt;
            href: href,&lt;br /&gt;
            text: text || &#039;알림&#039;&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setNotificationIcon(hasItems) {&lt;br /&gt;
    var quickIcon = document.getElementById(&#039;profile-quick-notification-icon&#039;);&lt;br /&gt;
    var svg = hasItems ? CLBI_SVG_BELL_DOT : CLBI_SVG_BELL;&lt;br /&gt;
&lt;br /&gt;
    if (quickIcon) {&lt;br /&gt;
        quickIcon.innerHTML = svg;&lt;br /&gt;
        quickIcon.classList.toggle(&#039;has-notifications&#039;, !!hasItems);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderNotificationPopup(items) {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    var badge = document.getElementById(&#039;clbi-notification-badge&#039;);&lt;br /&gt;
    if (!list) return;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;표시할 알림이 없습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        if (badge) badge.style.display = &#039;none&#039;;&lt;br /&gt;
        setNotificationIcon(false);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
    for (var i = 0; i &amp;lt; items.length; i++) {&lt;br /&gt;
        html +=&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;&#039; + items[i].href + &#039;&amp;quot; class=&amp;quot;clbi-notification-item&amp;quot; data-notification-id=&amp;quot;&#039; + (items[i].id || &#039;&#039;) + &#039;&amp;quot; style=&amp;quot;display:block;padding:10px 12px;color:#E2E2E2 !important;text-decoration:none !important;border-bottom:1px solid #1f1f1f;line-height:1.5;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                items[i].text +&lt;br /&gt;
            &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    list.innerHTML = html;&lt;br /&gt;
&lt;br /&gt;
    if (badge) {&lt;br /&gt;
        badge.textContent = items.length;&lt;br /&gt;
        badge.style.display = &#039;block&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    setNotificationIcon(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadNotificationsIntoPopup() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetch(&#039;/index.php?title=Special:Notifications&#039;, { credentials: &#039;same-origin&#039; })&lt;br /&gt;
        .then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        })&lt;br /&gt;
        .then(function(html) {&lt;br /&gt;
            var items = parseNotificationItemsFromHtml(html);&lt;br /&gt;
            renderNotificationPopup(items);&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function(err) {&lt;br /&gt;
            console.error(err);&lt;br /&gt;
            if (list) {&lt;br /&gt;
                list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;알림을 불러오지 못했습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markAllNotificationsRead() {&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: &#039;all&#039;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markNotificationReadById(notificationId) {&lt;br /&gt;
    if (!notificationId) {&lt;br /&gt;
        return $.Deferred().resolve().promise();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: notificationId&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initNotifications() {&lt;br /&gt;
    var quickBtn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!quickBtn) return;&lt;br /&gt;
&lt;br /&gt;
    ensureNotificationPopup();&lt;br /&gt;
    loadNotificationsIntoPopup();&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationToggle&#039;, &#039;#profile-quick-notifications&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (popup.style.display === &#039;none&#039; || popup.style.display === &#039;&#039;) {&lt;br /&gt;
                popup.style.display = &#039;block&#039;;&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
            } else {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationOutside&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationOutside&#039;, function(e) {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            var quickToggle = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (!popup.contains(e.target) &amp;amp;&amp;amp; (!quickToggle || !quickToggle.contains(e.target))) {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationReadAll&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationReadAll&#039;, &#039;#clbi-notification-readall&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var button = this;&lt;br /&gt;
            button.disabled = true;&lt;br /&gt;
            button.textContent = &#039;처리 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            markAllNotificationsRead()&lt;br /&gt;
                .then(function() {&lt;br /&gt;
                    loadNotificationsIntoPopup();&lt;br /&gt;
                })&lt;br /&gt;
                .always(function() {&lt;br /&gt;
                    button.disabled = false;&lt;br /&gt;
                    button.textContent = &#039;전체 읽음&#039;;&lt;br /&gt;
                });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationItem&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationItem&#039;, &#039;.clbi-notification-item&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var href = this.getAttribute(&#039;href&#039;);&lt;br /&gt;
            var notificationId = this.getAttribute(&#039;data-notification-id&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            markNotificationReadById(notificationId).always(function() {&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
                if (href) {&lt;br /&gt;
                    window.location.href = href;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(window)&lt;br /&gt;
        .off(&#039;resize.clbiNotification&#039;)&lt;br /&gt;
        .on(&#039;resize.clbiNotification&#039;, function() {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (popup &amp;amp;&amp;amp; popup.style.display === &#039;block&#039;) {&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
// ========== 알림 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
function initUserProfilePage() {&lt;br /&gt;
    $(&#039;body&#039;).addClass(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
&lt;br /&gt;
    var saveBtn = document.getElementById(&#039;pref-save&#039;);&lt;br /&gt;
    if (!saveBtn) return;&lt;br /&gt;
&lt;br /&gt;
    function getPrefRow(id) {&lt;br /&gt;
        var el = document.getElementById(id);&lt;br /&gt;
        if (!el) return null;&lt;br /&gt;
        return el.closest(&#039;.clbi-pref-row&#039;) || el.parentNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removePrefRow(id) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (row &amp;amp;&amp;amp; row.parentNode) {&lt;br /&gt;
            row.parentNode.removeChild(row);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPrefSection(className, titleText) {&lt;br /&gt;
        var section = document.createElement(&#039;div&#039;);&lt;br /&gt;
        section.className = &#039;clbi-pref-section &#039; + className;&lt;br /&gt;
&lt;br /&gt;
        var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;clbi-pref-section-title&#039;;&lt;br /&gt;
        title.textContent = titleText;&lt;br /&gt;
&lt;br /&gt;
        var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
        body.className = &#039;clbi-pref-section-body&#039;;&lt;br /&gt;
&lt;br /&gt;
        section.appendChild(title);&lt;br /&gt;
        section.appendChild(body);&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            section: section,&lt;br /&gt;
            body: body&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveRowToSection(id, targetBody, className) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (!row || !targetBody) return false;&lt;br /&gt;
&lt;br /&gt;
        row.classList.add(&#039;clbi-pref-row-key-&#039; + className);&lt;br /&gt;
        targetBody.appendChild(row);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebuildProfileSettingsLayout() {&lt;br /&gt;
        var root = document.querySelector(&#039;.clbi-prefs-profile&#039;);&lt;br /&gt;
        if (!root || root.dataset.profileSettingsReworked === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        root.dataset.profileSettingsReworked = &#039;1&#039;;&lt;br /&gt;
        root.classList.add(&#039;profile-settings-console&#039;);&lt;br /&gt;
&lt;br /&gt;
        removePrefRow(&#039;pref-badges&#039;);&lt;br /&gt;
&lt;br /&gt;
        var originalRows = Array.prototype.slice.call(root.querySelectorAll(&#039;.clbi-pref-row&#039;));&lt;br /&gt;
        var actionNodes = [];&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn.parentNode === root || saveBtn.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(saveBtn);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var statusNode = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        if (statusNode &amp;amp;&amp;amp; statusNode.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(statusNode);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var main = document.createElement(&#039;div&#039;);&lt;br /&gt;
        main.className = &#039;clbi-pref-main-grid&#039;;&lt;br /&gt;
&lt;br /&gt;
        var media = createPrefSection(&#039;clbi-pref-section-media&#039;, &#039;PROFILE IMAGE&#039;);&lt;br /&gt;
        var identity = createPrefSection(&#039;clbi-pref-section-identity&#039;, &#039;IDENTITY RECORD&#039;);&lt;br /&gt;
        var bio = createPrefSection(&#039;clbi-pref-section-bio&#039;, &#039;BIOGRAPHY&#039;);&lt;br /&gt;
        var account = createPrefSection(&#039;clbi-pref-section-account&#039;, &#039;ACCOUNT CONTACT&#039;);&lt;br /&gt;
        var misc = createPrefSection(&#039;clbi-pref-section-misc&#039;, &#039;OTHER OPTIONS&#039;);&lt;br /&gt;
&lt;br /&gt;
        main.appendChild(media.section);&lt;br /&gt;
        main.appendChild(identity.section);&lt;br /&gt;
        main.appendChild(bio.section);&lt;br /&gt;
        main.appendChild(account.section);&lt;br /&gt;
        main.appendChild(misc.section);&lt;br /&gt;
&lt;br /&gt;
        root.innerHTML = &#039;&#039;;&lt;br /&gt;
        root.appendChild(main);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-preview&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-btn&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-input&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-name&#039;, identity.body, &#039;name&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-role&#039;, identity.body, &#039;role&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-discord&#039;, identity.body, &#039;discord&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-bio&#039;, bio.body, &#039;bio&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-new-email&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-password&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-save&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
&lt;br /&gt;
        originalRows.forEach(function (row) {&lt;br /&gt;
            if (!row.parentNode &amp;amp;&amp;amp; !row.className.match(/clbi-pref-row-key-/)) {&lt;br /&gt;
                misc.body.appendChild(row);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!misc.body.children.length) {&lt;br /&gt;
            misc.section.parentNode.removeChild(misc.section);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var actions = document.createElement(&#039;div&#039;);&lt;br /&gt;
        actions.className = &#039;clbi-pref-actions&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn) actions.appendChild(saveBtn);&lt;br /&gt;
        if (statusNode) actions.appendChild(statusNode);&lt;br /&gt;
&lt;br /&gt;
        root.appendChild(actions);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    rebuildProfileSettingsLayout();&lt;br /&gt;
&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    var selectedFile = null;&lt;br /&gt;
    var cropper = null;&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-gallery-modal&#039;)) {&lt;br /&gt;
        var gModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        gModal.id = &#039;clbi-gallery-modal&#039;;&lt;br /&gt;
        gModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        gModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:480px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;justify-content:space-between;align-items:center;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;프로필 사진 선택&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-close&amp;quot; style=&amp;quot;background:none;border:none;color:#aaa;font-size:18px;cursor:pointer;&amp;quot;&amp;gt;✕&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-upload-btn&amp;quot; style=&amp;quot;background:#2a2a2a;border:2px dashed #854369;border-radius:8px;padding:32px;color:#e2e2e2;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:8px;font-size:13px;width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:32px;&amp;quot;&amp;gt;🖼️&amp;lt;/span&amp;gt;새 사진 업로드&#039; +&lt;br /&gt;
                &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history-section&amp;quot; style=&amp;quot;display:none;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div style=&amp;quot;font-size:11px;color:#888;margin-bottom:8px;&amp;quot;&amp;gt;이전 사진 — 클릭하면 바로 적용&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history&amp;quot; style=&amp;quot;display:flex;gap:8px;flex-wrap:wrap;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(gModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-crop-modal&#039;)) {&lt;br /&gt;
        var cModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        cModal.id = &#039;clbi-crop-modal&#039;;&lt;br /&gt;
        cModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        cModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:500px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;사진 조정&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;width:100%;max-height:380px;overflow:hidden;border-radius:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img id=&amp;quot;clbi-crop-image&amp;quot; style=&amp;quot;max-width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;gap:8px;justify-content:flex-end;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-cancel&amp;quot; style=&amp;quot;background:#2a2a2a;color:#e2e2e2;border:1px solid #444;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;취소&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-confirm&amp;quot; style=&amp;quot;background:#854369;color:#fff;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;확정&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(cModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gModal = document.getElementById(&#039;clbi-gallery-modal&#039;);&lt;br /&gt;
    var cModal = document.getElementById(&#039;clbi-crop-modal&#039;);&lt;br /&gt;
    var cropImage = document.getElementById(&#039;clbi-crop-image&#039;);&lt;br /&gt;
    var pfpInput = document.getElementById(&#039;pref-pfp-input&#039;);&lt;br /&gt;
&lt;br /&gt;
    function openGallery() {&lt;br /&gt;
        gModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;query&#039;,&lt;br /&gt;
            titles: &#039;파일:Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
            prop: &#039;imageinfo&#039;,&lt;br /&gt;
            iiprop: &#039;url|timestamp&#039;,&lt;br /&gt;
            iilimit: 6&lt;br /&gt;
        }).then(function(data) {&lt;br /&gt;
            var pages = data.query.pages;&lt;br /&gt;
            var page = pages[Object.keys(pages)[0]];&lt;br /&gt;
            if (!page.imageinfo || page.imageinfo.length === 0) return;&lt;br /&gt;
&lt;br /&gt;
            var historyEl = document.getElementById(&#039;clbi-gallery-history&#039;);&lt;br /&gt;
            var sectionEl = document.getElementById(&#039;clbi-gallery-history-section&#039;);&lt;br /&gt;
            historyEl.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            page.imageinfo.forEach(function(info, idx) {&lt;br /&gt;
                var wrap = document.createElement(&#039;div&#039;);&lt;br /&gt;
                wrap.style.cssText = &#039;position:relative;cursor:pointer;&#039;;&lt;br /&gt;
&lt;br /&gt;
                var img = document.createElement(&#039;img&#039;);&lt;br /&gt;
                img.src = info.url;&lt;br /&gt;
                img.style.cssText =&lt;br /&gt;
                    &#039;width:72px;height:72px;object-fit:cover;border-radius:8px;border:2px solid #444;flex-shrink:0;&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (idx === 0) {&lt;br /&gt;
                    img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                    var badge = document.createElement(&#039;div&#039;);&lt;br /&gt;
                    badge.textContent = &#039;현재&#039;;&lt;br /&gt;
                    badge.style.cssText =&lt;br /&gt;
                        &#039;position:absolute;bottom:4px;left:50%;transform:translateX(-50%);background:#854369;color:#fff;font-size:9px;padding:1px 6px;border-radius:10px;&#039;;&lt;br /&gt;
                    wrap.appendChild(badge);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseenter&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseleave&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#444&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
                    fetch(info.url)&lt;br /&gt;
                        .then(function(r) {&lt;br /&gt;
                            return r.blob();&lt;br /&gt;
                        })&lt;br /&gt;
                        .then(function(blob) {&lt;br /&gt;
                            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
                            gModal.style.display = &#039;none&#039;;&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                wrap.appendChild(img);&lt;br /&gt;
                historyEl.appendChild(wrap);&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            sectionEl.style.display = &#039;block&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function openCrop(src) {&lt;br /&gt;
        cropImage.src = src;&lt;br /&gt;
        cModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            cropper = new Cropper(cropImage, {&lt;br /&gt;
                aspectRatio: 1,&lt;br /&gt;
                viewMode: 1,&lt;br /&gt;
                dragMode: &#039;move&#039;,&lt;br /&gt;
                autoCropArea: 0.8,&lt;br /&gt;
                cropBoxResizable: true,&lt;br /&gt;
                cropBoxMovable: true&lt;br /&gt;
            });&lt;br /&gt;
        }, 150);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;pref-pfp-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        openGallery();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-upload-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        pfpInput.click();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-close&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    pfpInput.addEventListener(&#039;change&#039;, function() {&lt;br /&gt;
        var file = this.files[0];&lt;br /&gt;
        if (!file) return;&lt;br /&gt;
&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
&lt;br /&gt;
        var reader = new FileReader();&lt;br /&gt;
        reader.onload = function(e) {&lt;br /&gt;
            openCrop(e.target.result);&lt;br /&gt;
        };&lt;br /&gt;
        reader.readAsDataURL(file);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-cancel&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        cModal.style.display = &#039;none&#039;;&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
        pfpInput.value = &#039;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-confirm&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        if (!cropper) return;&lt;br /&gt;
&lt;br /&gt;
        var canvas = cropper.getCroppedCanvas({ width: 256, height: 256 });&lt;br /&gt;
        if (!canvas) return;&lt;br /&gt;
&lt;br /&gt;
        canvas.toBlob(function(blob) {&lt;br /&gt;
            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
            cModal.style.display = &#039;none&#039;;&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
        }, &#039;image/png&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var emailSaveBtn = document.getElementById(&#039;pref-email-save&#039;);&lt;br /&gt;
    if (emailSaveBtn) {&lt;br /&gt;
        emailSaveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
            var statusEl = document.getElementById(&#039;pref-email-status&#039;);&lt;br /&gt;
            var newEmail = document.getElementById(&#039;pref-new-email&#039;).value;&lt;br /&gt;
            var password = document.getElementById(&#039;pref-email-password&#039;).value;&lt;br /&gt;
&lt;br /&gt;
            if (!newEmail || !password) {&lt;br /&gt;
                statusEl.textContent = &#039;이메일과 비밀번호를 입력해주세요.&#039;;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            statusEl.textContent = &#039;변경 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;changeemail&#039;,&lt;br /&gt;
                email: newEmail,&lt;br /&gt;
                password: password&lt;br /&gt;
            }).then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 이메일 변경됨&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-new-email&#039;).value = &#039;&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-email-password&#039;).value = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 3000);&lt;br /&gt;
            }).fail(function(code, data) {&lt;br /&gt;
                var msg = data &amp;amp;&amp;amp; data.error &amp;amp;&amp;amp; data.error.info ? data.error.info : &#039;변경 실패&#039;;&lt;br /&gt;
                statusEl.textContent = msg;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    saveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        var statusEl = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        statusEl.textContent = &#039;저장 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
        var promises = [];&lt;br /&gt;
&lt;br /&gt;
        if (selectedFile) {&lt;br /&gt;
            var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;upload&#039;,&lt;br /&gt;
                    filename: &#039;Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
                    ignorewarnings: true,&lt;br /&gt;
                    file: selectedFile,&lt;br /&gt;
                    format: &#039;json&#039;&lt;br /&gt;
                }, {&lt;br /&gt;
                    contentType: &#039;multipart/form-data&#039;&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var fields = [&#039;name&#039;, &#039;discord&#039;, &#039;role&#039;, &#039;bio&#039;];&lt;br /&gt;
&lt;br /&gt;
        for (var i = 0; i &amp;lt; fields.length; i++) {&lt;br /&gt;
            var el = document.getElementById(&#039;pref-&#039; + fields[i]);&lt;br /&gt;
            if (!el) continue;&lt;br /&gt;
&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;options&#039;,&lt;br /&gt;
                    optionname: &#039;profile-&#039; + fields[i],&lt;br /&gt;
                    optionvalue: el.value&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $.when.apply($, promises)&lt;br /&gt;
            .then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 저장됨&#039;;&lt;br /&gt;
                selectedFile = null;&lt;br /&gt;
                document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;사진 선택&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 2000);&lt;br /&gt;
            })&lt;br /&gt;
            .fail(function() {&lt;br /&gt;
                statusEl.textContent = &#039;저장 실패&#039;;&lt;br /&gt;
            });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Banner / CRT Page Monitor thumbnail slices&lt;br /&gt;
   - base 이미지는 틀에 들어간 파일 문법 그대로 사용&lt;br /&gt;
   - slice 레이어에는 300px MediaWiki 썸네일만 삽입&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function ($, mw) {&lt;br /&gt;
    var thumbCache = {};&lt;br /&gt;
&lt;br /&gt;
    function parseSliceWidth(value) {&lt;br /&gt;
        var parsed = parseInt(value, 10);&lt;br /&gt;
&lt;br /&gt;
        if (!isFinite(parsed) || parsed &amp;lt; 120) {&lt;br /&gt;
            return 300;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parsed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getImageSrc(img) {&lt;br /&gt;
        return img ? (img.currentSrc || img.getAttribute(&#039;src&#039;) || img.src || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFileNameFromSrc(src) {&lt;br /&gt;
        var a;&lt;br /&gt;
        var parts;&lt;br /&gt;
        var fileName;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = src;&lt;br /&gt;
&lt;br /&gt;
        parts = (a.pathname || &#039;&#039;).split(&#039;/&#039;).filter(function (part) {&lt;br /&gt;
            return !!part;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!parts.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        fileName = parts.pop();&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * MediaWiki thumb URL 예시:&lt;br /&gt;
         * /images/thumb/a/ab/File.png/1000px-File.png&lt;br /&gt;
         * /images/thumb/a/ab/File.svg/1000px-File.svg.png&lt;br /&gt;
         *&lt;br /&gt;
         * 이 경우 실제 파일명은 마지막 조각이 아니라 그 앞 조각이다.&lt;br /&gt;
         */&lt;br /&gt;
        if (/^\d+px-/.test(fileName) &amp;amp;&amp;amp; parts.length) {&lt;br /&gt;
            fileName = parts.pop();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fileName = fileName.replace(/^\d+px-/, &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            fileName = decodeURIComponent(fileName);&lt;br /&gt;
        } catch (e) {}&lt;br /&gt;
&lt;br /&gt;
        return fileName;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveThumbUrl(img, width, callback) {&lt;br /&gt;
        var src = getImageSrc(img);&lt;br /&gt;
        var fileName = getFileNameFromSrc(src);&lt;br /&gt;
        var cacheKey;&lt;br /&gt;
        var entry;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return;&lt;br /&gt;
&lt;br /&gt;
        if (!fileName || !mw || !mw.loader) {&lt;br /&gt;
            callback(src);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        cacheKey = fileName + &#039;|&#039; + width;&lt;br /&gt;
        entry = thumbCache[cacheKey];&lt;br /&gt;
&lt;br /&gt;
        if (entry) {&lt;br /&gt;
            if (entry.resolved) {&lt;br /&gt;
                callback(entry.url || src);&lt;br /&gt;
            } else {&lt;br /&gt;
                entry.callbacks.push(callback);&lt;br /&gt;
            }&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        entry = {&lt;br /&gt;
            resolved: false,&lt;br /&gt;
            url: &#039;&#039;,&lt;br /&gt;
            callbacks: [callback]&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        thumbCache[cacheKey] = entry;&lt;br /&gt;
&lt;br /&gt;
        function finish(url) {&lt;br /&gt;
            var callbacks = entry.callbacks.slice();&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            entry.resolved = true;&lt;br /&gt;
            entry.url = url || src;&lt;br /&gt;
            entry.callbacks = [];&lt;br /&gt;
&lt;br /&gt;
            for (i = 0; i &amp;lt; callbacks.length; i++) {&lt;br /&gt;
                callbacks[i](entry.url);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using(&#039;mediawiki.api&#039;).done(function () {&lt;br /&gt;
            var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + fileName,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url&#039;,&lt;br /&gt;
                iiurlwidth: width,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).done(function (data) {&lt;br /&gt;
                var page;&lt;br /&gt;
                var info;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    data &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages.length&lt;br /&gt;
                ) {&lt;br /&gt;
                    page = data.query.pages[0];&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        page &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo.length&lt;br /&gt;
                    ) {&lt;br /&gt;
                        info = page.imageinfo[0];&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                finish((info &amp;amp;&amp;amp; (info.thumburl || info.url)) || src);&lt;br /&gt;
            }).fail(function () {&lt;br /&gt;
                finish(src);&lt;br /&gt;
            });&lt;br /&gt;
        }).fail(function () {&lt;br /&gt;
            finish(src);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySliceImages(frame, thumbUrl) {&lt;br /&gt;
        var slices;&lt;br /&gt;
        var i;&lt;br /&gt;
        var img;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !thumbUrl) return;&lt;br /&gt;
&lt;br /&gt;
        slices = frame.querySelectorAll(&#039;.crt-page-monitor-slice&#039;);&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; slices.length; i++) {&lt;br /&gt;
            slices[i].innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            img = document.createElement(&#039;img&#039;);&lt;br /&gt;
            img.className = &#039;crt-page-monitor-slice-img&#039;;&lt;br /&gt;
            img.src = thumbUrl;&lt;br /&gt;
            img.alt = &#039;&#039;;&lt;br /&gt;
            img.decoding = &#039;async&#039;;&lt;br /&gt;
            img.loading = &#039;eager&#039;;&lt;br /&gt;
            img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
            slices[i].appendChild(img);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        frame.setAttribute(&#039;data-crt-slices-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrame(frame) {&lt;br /&gt;
        var baseImg;&lt;br /&gt;
        var width;&lt;br /&gt;
&lt;br /&gt;
        if (!frame) return;&lt;br /&gt;
        if (frame.getAttribute(&#039;data-crt-slices-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        baseImg = frame.querySelector(&#039;.crt-page-monitor-image-base img&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!baseImg) return;&lt;br /&gt;
&lt;br /&gt;
        width = parseSliceWidth(frame.getAttribute(&#039;data-crt-slice-width&#039;));&lt;br /&gt;
&lt;br /&gt;
        resolveThumbUrl(baseImg, width, function (thumbUrl) {&lt;br /&gt;
            if (!frame || !frame.parentNode) return;&lt;br /&gt;
            applySliceImages(frame, thumbUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrames(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var frames = scope.querySelectorAll(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; frames.length; i++) {&lt;br /&gt;
            initBannerFrame(frames[i]);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initBannerFrames(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initBannerFrames($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(jQuery, window.mw);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Tab System — tab switching UI&lt;br /&gt;
   글리치 플리커 + RGB split + 방향 슬라이드&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function initDocTabs() {&lt;br /&gt;
        var tabBars = document.querySelectorAll(&#039;.doc-tab-bar&#039;);&lt;br /&gt;
        if (!tabBars.length) return;&lt;br /&gt;
&lt;br /&gt;
        tabBars.forEach(function (bar) {&lt;br /&gt;
            if (bar.getAttribute(&#039;data-tabs-init&#039;)) return;&lt;br /&gt;
            bar.setAttribute(&#039;data-tabs-init&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            var tabs = Array.from(bar.querySelectorAll(&#039;.doc-tab&#039;));&lt;br /&gt;
            if (!tabs.length) return;&lt;br /&gt;
&lt;br /&gt;
            var panel = bar.closest(&#039;.doc-panel&#039;);&lt;br /&gt;
            var display = panel ? panel.querySelector(&#039;.doc-display&#039;) : null;&lt;br /&gt;
            if (!display) display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
            if (!display) return;&lt;br /&gt;
&lt;br /&gt;
            tabs.forEach(function (tab, i) {&lt;br /&gt;
                tab.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
                    var currentIdx = tabs.findIndex(function (t) {&lt;br /&gt;
                        return t.classList.contains(&#039;active&#039;);&lt;br /&gt;
                    });&lt;br /&gt;
                    if (currentIdx === i) return;&lt;br /&gt;
                    switchTab(tabs, display, i, i &amp;gt; currentIdx ? 1 : -1);&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            var initIdx = tabs.findIndex(function (t) { return t.classList.contains(&#039;active&#039;); });&lt;br /&gt;
            if (initIdx !== -1) {&lt;br /&gt;
                var initRef = tabs[initIdx].dataset.ref;&lt;br /&gt;
                var initEl = initRef ? document.getElementById(initRef) : null;&lt;br /&gt;
                display.innerHTML = initEl ? initEl.innerHTML : (tabs[initIdx].dataset.content || &#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var isAnimating = false;&lt;br /&gt;
&lt;br /&gt;
    function switchTab(tabs, display, nextIdx, dir) {&lt;br /&gt;
        if (isAnimating) return;&lt;br /&gt;
        isAnimating = true;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (t) { t.classList.remove(&#039;active&#039;); });&lt;br /&gt;
        tabs[nextIdx].classList.add(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
        var ref = tabs[nextIdx].dataset.ref;&lt;br /&gt;
        var nextContent;&lt;br /&gt;
        if (ref) {&lt;br /&gt;
            var refEl = document.getElementById(ref);&lt;br /&gt;
            nextContent = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            nextContent = tabs[nextIdx].dataset.content || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        glitchOut(display, dir, function () {&lt;br /&gt;
            display.innerHTML = nextContent;&lt;br /&gt;
            glitchIn(display, dir, function () {&lt;br /&gt;
                isAnimating = false;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchOut(el, dir, cb) {&lt;br /&gt;
        var duration = 160;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var slideX = dir * 16;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = p * p;&lt;br /&gt;
&lt;br /&gt;
            var tx = slideX * ease;&lt;br /&gt;
            var skew = dir * ease * 1.0;&lt;br /&gt;
            var opacity = 1 - ease;&lt;br /&gt;
            var rgb = ease * 5;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.75)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.65)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + (1 + ease * 0.25) + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.opacity = &#039;0&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchIn(el, dir, cb) {&lt;br /&gt;
        var duration = 200;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var startX = -dir * 16;&lt;br /&gt;
&lt;br /&gt;
        el.style.transform = &#039;translateX(&#039; + startX + &#039;px) skewX(&#039; + (-dir * 1.0) + &#039;deg)&#039;;&lt;br /&gt;
        el.style.opacity = &#039;0&#039;;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = 1 - Math.pow(1 - p, 3);&lt;br /&gt;
&lt;br /&gt;
            var tx = startX * (1 - ease);&lt;br /&gt;
            var skew = -dir * 1.0 * (1 - ease);&lt;br /&gt;
            var opacity = ease;&lt;br /&gt;
            var rgb = (1 - ease) * 3;&lt;br /&gt;
            var brightness = 1 + (1 - ease) * 0.35;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.65)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.55)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + brightness + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.transform = &#039;&#039;;&lt;br /&gt;
                el.style.opacity = &#039;&#039;;&lt;br /&gt;
                el.style.filter = &#039;&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initDocTabs);&lt;br /&gt;
} else {&lt;br /&gt;
    initDocTabs();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        initDocTabs();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Section Switch — 좌측 섹션 전환&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;.doc-nav-item[data-section]&#039;, function () {&lt;br /&gt;
    var name = $(this).attr(&#039;data-section&#039;);&lt;br /&gt;
    var display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
    var titleEl = document.getElementById(&#039;doc-center-title&#039;);&lt;br /&gt;
    var tabBar = document.getElementById(&#039;doc-tab-bar-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!display) return;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section]&#039;).removeClass(&#039;active&#039;);&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section=&amp;quot;&#039; + name + &#039;&amp;quot;]&#039;).addClass(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (name === &#039;text&#039;) {&lt;br /&gt;
        if (titleEl) titleEl.textContent = &#039;개요&#039;;&lt;br /&gt;
        if (tabBar) $(tabBar).show();&lt;br /&gt;
        var activeTab = tabBar ? tabBar.querySelector(&#039;.doc-tab.active&#039;) : null;&lt;br /&gt;
        if (!activeTab &amp;amp;&amp;amp; tabBar) activeTab = tabBar.querySelector(&#039;.doc-tab&#039;);&lt;br /&gt;
        if (activeTab) {&lt;br /&gt;
            var ref = activeTab.dataset.ref;&lt;br /&gt;
            var refEl = ref ? document.getElementById(ref) : null;&lt;br /&gt;
            display.innerHTML = refEl ? refEl.innerHTML : (activeTab.dataset.content || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        if (titleEl) titleEl.textContent = name === &#039;factions&#039; ? &#039;세력&#039; : name === &#039;people&#039; ? &#039;인물&#039; : name;&lt;br /&gt;
        if (tabBar) $(tabBar).hide();&lt;br /&gt;
        var refEl = document.getElementById(&#039;doc-content-&#039; + name);&lt;br /&gt;
        display.innerHTML = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   CRT WebGL Renderer — cool-retro-term IBM DOS style&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function createNoiseTexture(gl) {&lt;br /&gt;
        var size = 512;&lt;br /&gt;
        var data = new Uint8Array(size * size * 4);&lt;br /&gt;
        var s = 12345;&lt;br /&gt;
        function rand() {&lt;br /&gt;
            s = (s * 1664525 + 1013904223) &amp;amp; 0xffffffff;&lt;br /&gt;
            return (s &amp;gt;&amp;gt;&amp;gt; 0) / 0xffffffff;&lt;br /&gt;
        }&lt;br /&gt;
        for (var i = 0; i &amp;lt; data.length; i++) {&lt;br /&gt;
            data[i] = (rand() * 255) | 0;&lt;br /&gt;
        }&lt;br /&gt;
        var tex = gl.createTexture();&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, tex);&lt;br /&gt;
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
        return tex;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var VERT = [&lt;br /&gt;
        &#039;attribute vec2 a_pos;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5);&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_pos, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var FRAG = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_tex;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_noise;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_res;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_imgSize;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_noiseScale;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float sum2(vec2 v) { return v.x + v.y; }&#039;,&lt;br /&gt;
        &#039;float min2(vec2 v) { return min(v.x, v.y); }&#039;,&lt;br /&gt;
        &#039;float rgb2grey(vec3 v) { return dot(v, vec3(0.21, 0.72, 0.04)); }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 coverUV(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  float imgAR = u_imgSize.x / u_imgSize.y;&#039;,&lt;br /&gt;
        &#039;  float scrAR = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  float scale = imgAR / scrAR;&#039;,&lt;br /&gt;
        &#039;  float offsetY = (1.0 - scale) * 0.5;&#039;,&lt;br /&gt;
        &#039;  return vec2(uv.x, uv.y * scale + offsetY);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 barrel(vec2 v, vec2 cc, float k) {&#039;,&lt;br /&gt;
        &#039;  float ar = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  vec2 c2 = cc;&#039;,&lt;br /&gt;
        &#039;  if (ar &amp;gt; 1.0) c2.x /= ar; else c2.y *= ar;&#039;,&lt;br /&gt;
        &#039;  float dist = dot(c2, c2) * k;&#039;,&lt;br /&gt;
        &#039;  return v - cc * (1.0 + dist) * dist;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleInitialNoise(float t) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, vec2(fract(t/2048.0), fract(t/1048576.0)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleScreenNoise(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, u_noiseScale * uv);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyRgbShift(vec2 texUV, float shift) {&#039;,&lt;br /&gt;
        &#039;  vec2 d = vec2(shift, 0.0);&#039;,&lt;br /&gt;
        &#039;  vec3 r = texture2D(u_tex, clamp(texUV + d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 c = texture2D(u_tex, texUV).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 l = texture2D(u_tex, clamp(texUV - d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  return vec3(&#039;,&lt;br /&gt;
        &#039;    l.r*0.10 + r.r*0.30 + c.r*0.60,&#039;,&lt;br /&gt;
        &#039;    l.g*0.20 + r.g*0.20 + c.g*0.60,&#039;,&lt;br /&gt;
        &#039;    l.b*0.30 + r.b*0.10 + c.b*0.60&#039;,&lt;br /&gt;
        &#039;  );&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyBloom(vec2 texUV, float strength) {&#039;,&lt;br /&gt;
        &#039;  vec2 px = 2.0 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 acc = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0,  px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0, -px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  return acc / 6.0 * strength;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyScanlines(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
        &#039;  float line = mod(uv.y * u_res.y, 2.0);&#039;,&lt;br /&gt;
        &#039;  vec3 hi = ((1.0 + 0.30) - 0.2 * col) * col;&#039;,&lt;br /&gt;
        &#039;  vec3 lo = ((1.0 - 0.30) + 0.1 * col) * col;&#039;,&lt;br /&gt;
        &#039;  return line &amp;lt; 1.0 ? lo : hi;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
&#039;vec3 applyRasterization(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
&#039;  float t = u_time;&#039;,&lt;br /&gt;
&#039;  vec2 noiseUV = uv + vec2(fract(t * 0.030), fract(t * 0.060));&#039;,&lt;br /&gt;
&#039;  float wobbleX = (texture2D(u_noise, noiseUV * 0.8).r - 0.5) * 0.0018;&#039;,&lt;br /&gt;
&#039;  float wobbleY = (texture2D(u_noise, noiseUV * 0.8 + 0.5).r - 0.5) * 0.0008;&#039;,&lt;br /&gt;
&#039;  vec2 wobbledUV = clamp(uv + vec2(wobbleX, wobbleY), 0.0, 1.0);&#039;,&lt;br /&gt;
&#039;  vec3 wobbled = texture2D(u_tex, wobbledUV).rgb;&#039;,&lt;br /&gt;
&#039;  return mix(col, wobbled, 0.35);&#039;,&lt;br /&gt;
&#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float glowingLine(vec2 uv, float t) {&#039;,&lt;br /&gt;
&#039;  float pos = fract(t * 0.2);&#039;,&lt;br /&gt;
&#039;  float lineY = pos * (u_res.y + 330.0) - 120.0;&#039;,&lt;br /&gt;
        &#039;  float y = uv.y * u_res.y;&#039;,&lt;br /&gt;
        &#039;  return fract(smoothstep(-300.0, 0.0, y - lineY));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 applyHSync(vec2 uv, vec4 noise, float strength) {&#039;,&lt;br /&gt;
        &#039;  float randval = strength - noise.r;&#039;,&lt;br /&gt;
        &#039;  float scale = step(0.0, randval) * randval * strength;&#039;,&lt;br /&gt;
        &#039;  float freq = mix(4.0, 40.0, noise.g);&#039;,&lt;br /&gt;
        &#039;  uv.x += sin((uv.y + u_time * 0.001) * freq) * scale;&#039;,&lt;br /&gt;
        &#039;  return uv;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 cc = vec2(0.5) - v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float curvature = 0.18;&#039;,&lt;br /&gt;
        &#039;  vec2 uv = barrel(v_uv, cc, curvature);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float inScreen = min2(step(vec2(0.0), uv) - step(vec2(1.0), uv));&#039;,&lt;br /&gt;
        &#039;  if (inScreen &amp;lt; 0.5) { gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); return; }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 texUV = clamp(coverUV(uv), 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec4 initNoise = sampleInitialNoise(u_time);&#039;,&lt;br /&gt;
        &#039;  vec4 screenNoise = sampleScreenNoise(uv);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV = applyHSync(texUV, initNoise, 0.006);&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV += (vec2(screenNoise.b, screenNoise.a) - 0.5) * 0.0006;&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec3 col = applyRgbShift(texUV, 0.003);&#039;,&lt;br /&gt;
        &#039;  col += applyBloom(texUV, 0.22);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 bpx = 1.5 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 blurCol = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,   -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,    bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  col = mix(col, blurCol / 8.0, 0.40);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = applyScanlines(uv, col);&#039;,&lt;br /&gt;
        &#039;  col = applyRasterization(texUV, col);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float glow = glowingLine(uv, u_time);&#039;,&lt;br /&gt;
&#039;  col += glow * 0.08 * vec3(0.85, 0.95, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float dist = length(cc);&#039;,&lt;br /&gt;
        &#039;  col += screenNoise.a * 0.07 * (1.0 - dist * 1.3);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float grey = rgb2grey(col);&#039;,&lt;br /&gt;
        &#039;  vec3 phosphor = vec3(0.75, 0.88, 1.0);&#039;,&lt;br /&gt;
        &#039;  col = mix(col, grey * phosphor, 0.35);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 vig = v_uv * (1.0 - v_uv);&#039;,&lt;br /&gt;
        &#039;  col *= pow(vig.x * vig.y * 15.0, 0.25);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col *= 1.0 + (initNoise.g - 0.5) * 0.06;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col += vec3(0.012) * (1.0 - dist) * (1.0 - dist);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = pow(clamp(col, 0.0, 1.0), vec3(0.90));&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  gl_FragColor = vec4(col, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function initCRTCanvas(screen, imgEl) {&lt;br /&gt;
        var existing = screen.querySelector(&#039;.crt-webgl-canvas&#039;);&lt;br /&gt;
        if (existing) existing.remove();&lt;br /&gt;
&lt;br /&gt;
        var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.className = &#039;crt-webgl-canvas&#039;;&lt;br /&gt;
        canvas.style.cssText = &#039;position:absolute;inset:0;width:100%;height:100%;z-index:19;pointer-events:none;display:block;&#039;;&lt;br /&gt;
        screen.appendChild(canvas);&lt;br /&gt;
&lt;br /&gt;
        var gl = canvas.getContext(&#039;webgl&#039;) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
        if (!gl) return;&lt;br /&gt;
&lt;br /&gt;
        function compile(type, src) {&lt;br /&gt;
            var s = gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(s, src);&lt;br /&gt;
            gl.compileShader(s);&lt;br /&gt;
            if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {&lt;br /&gt;
                console.error(&#039;[CRT shader]&#039;, gl.getShaderInfoLog(s));&lt;br /&gt;
            }&lt;br /&gt;
            return s;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var prog = gl.createProgram();&lt;br /&gt;
        gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT));&lt;br /&gt;
        gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FRAG));&lt;br /&gt;
        gl.linkProgram(prog);&lt;br /&gt;
        gl.useProgram(prog);&lt;br /&gt;
&lt;br /&gt;
        var buf = gl.createBuffer();&lt;br /&gt;
        gl.bindBuffer(gl.ARRAY_BUFFER, buf);&lt;br /&gt;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);&lt;br /&gt;
        var aPos = gl.getAttribLocation(prog, &#039;a_pos&#039;);&lt;br /&gt;
        gl.enableVertexAttribArray(aPos);&lt;br /&gt;
        gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
        var uTex     = gl.getUniformLocation(prog, &#039;u_tex&#039;);&lt;br /&gt;
        var uNoise   = gl.getUniformLocation(prog, &#039;u_noise&#039;);&lt;br /&gt;
        var uRes     = gl.getUniformLocation(prog, &#039;u_res&#039;);&lt;br /&gt;
        var uImgSize = gl.getUniformLocation(prog, &#039;u_imgSize&#039;);&lt;br /&gt;
        var uTime    = gl.getUniformLocation(prog, &#039;u_time&#039;);&lt;br /&gt;
        var uNoiseSc = gl.getUniformLocation(prog, &#039;u_noiseScale&#039;);&lt;br /&gt;
&lt;br /&gt;
        var imgTex = gl.createTexture();&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE1);&lt;br /&gt;
        createNoiseTexture(gl);&lt;br /&gt;
&lt;br /&gt;
        var texReady = false;&lt;br /&gt;
        function uploadImg() {&lt;br /&gt;
            if (!imgEl || !imgEl.complete || !imgEl.naturalWidth) return;&lt;br /&gt;
            try {&lt;br /&gt;
                gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
                gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imgEl);&lt;br /&gt;
                texReady = true;&lt;br /&gt;
            } catch(e) { console.error(&#039;[CRT] tex:&#039;, e); }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var lastW = 0, lastH = 0;&lt;br /&gt;
        function resize() {&lt;br /&gt;
            var w = screen.offsetWidth, h = screen.offsetHeight;&lt;br /&gt;
            if (w === lastW &amp;amp;&amp;amp; h === lastH) return;&lt;br /&gt;
            lastW = w; lastH = h;&lt;br /&gt;
            canvas.width = w; canvas.height = h;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var raf;&lt;br /&gt;
        var t0 = performance.now();&lt;br /&gt;
&lt;br /&gt;
        function render() {&lt;br /&gt;
            raf = requestAnimationFrame(render);&lt;br /&gt;
            if (!texReady) { uploadImg(); return; }&lt;br /&gt;
            resize();&lt;br /&gt;
            var t = (performance.now() - t0) / 1000;&lt;br /&gt;
            gl.uniform1i(uTex, 0);&lt;br /&gt;
            gl.uniform1i(uNoise, 1);&lt;br /&gt;
            gl.uniform2f(uRes, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform2f(uImgSize, imgEl.naturalWidth, imgEl.naturalHeight);&lt;br /&gt;
            gl.uniform1f(uTime, t);&lt;br /&gt;
            gl.uniform2f(uNoiseSc, canvas.width / 512.0, canvas.height / 512.0);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) { uploadImg(); }&lt;br /&gt;
        else { imgEl.addEventListener(&#039;load&#039;, uploadImg); }&lt;br /&gt;
&lt;br /&gt;
        render();&lt;br /&gt;
        screen._crtCleanup = function () { cancelAnimationFrame(raf); };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initAllCRTScreens(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        scope.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (screen) {&lt;br /&gt;
            if (screen.getAttribute(&#039;data-crt-webgl&#039;) === &#039;1&#039;) return;&lt;br /&gt;
            screen.setAttribute(&#039;data-crt-webgl&#039;, &#039;1&#039;);&lt;br /&gt;
            var frame = screen.closest(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
            if (!frame) return;&lt;br /&gt;
            var imgEl = frame.querySelector(&#039;.crt-page-monitor-slice-img, .crt-page-monitor-image-base img&#039;);&lt;br /&gt;
            if (imgEl &amp;amp;&amp;amp; imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) {&lt;br /&gt;
                initCRTCanvas(screen, imgEl);&lt;br /&gt;
            } else if (imgEl) {&lt;br /&gt;
                imgEl.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, imgEl); });&lt;br /&gt;
            } else {&lt;br /&gt;
                var obs = new MutationObserver(function () {&lt;br /&gt;
                    var img = frame.querySelector(&#039;.crt-page-monitor-slice-img&#039;);&lt;br /&gt;
                    if (!img) return;&lt;br /&gt;
                    obs.disconnect();&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        initCRTCanvas(screen, img);&lt;br /&gt;
                    } else {&lt;br /&gt;
                        img.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, img); });&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
                obs.observe(frame, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () { initAllCRTScreens(document); });&lt;br /&gt;
&lt;br /&gt;
    if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($c) {&lt;br /&gt;
            document.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (s) {&lt;br /&gt;
                if (s._crtCleanup) s._crtCleanup();&lt;br /&gt;
                s.removeAttribute(&#039;data-crt-webgl&#039;);&lt;br /&gt;
            });&lt;br /&gt;
            initAllCRTScreens($c &amp;amp;&amp;amp; $c[0] ? $c[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Progress System UI&lt;br /&gt;
MediaWiki:Common.js controlled frontend&lt;br /&gt;
========================================= */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUiInitialized) return;&lt;br /&gt;
    window.ProgressSystemWebUiInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    var api = null;&lt;br /&gt;
&lt;br /&gt;
    function withApi(done, fail) {&lt;br /&gt;
        if (api) {&lt;br /&gt;
            done(api);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
            api = new mw.Api();&lt;br /&gt;
            done(api);&lt;br /&gt;
        }, function () {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var inFlightPageIds = new Set();&lt;br /&gt;
    var handledPageIds = new Set();&lt;br /&gt;
    var notificationQueue = [];&lt;br /&gt;
    var notificationActive = false;&lt;br /&gt;
    var summaryRequested = false;&lt;br /&gt;
    var currentSummary = null;&lt;br /&gt;
    var pendingSummary = null;&lt;br /&gt;
    var pendingOptions = null;&lt;br /&gt;
    var visibilityBound = false;&lt;br /&gt;
    var barTimerA = null;&lt;br /&gt;
    var barTimerB = null;&lt;br /&gt;
    var barTimerC = null;&lt;br /&gt;
    var summaryRetryTimer = null;&lt;br /&gt;
    var summaryRetryAttempts = 0;&lt;br /&gt;
&lt;br /&gt;
    function isLoggedIn() {&lt;br /&gt;
        return !!mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPageId() {&lt;br /&gt;
        var id = parseInt(mw.config.get(&#039;wgArticleId&#039;) || 0, 10);&lt;br /&gt;
        return Number.isFinite(id) ? id : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isRewardableClientSide() {&lt;br /&gt;
        if (!isLoggedIn()) return false;&lt;br /&gt;
        if (parseInt(mw.config.get(&#039;wgNamespaceNumber&#039;), 10) !== 0) return false;&lt;br /&gt;
        if (mw.config.get(&#039;wgIsMainPage&#039;)) return false;&lt;br /&gt;
        if (getPageId() &amp;lt;= 0) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPanelHtml() {&lt;br /&gt;
        return &#039;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;progress-panel&amp;quot; class=&amp;quot;profile-progress-block is-syncing&amp;quot; aria-live=&amp;quot;polite&amp;quot; data-progress-state=&amp;quot;syncing&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-title-row&amp;quot; hidden&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-level-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-level-label&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-gain&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-sub-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-xp-next&amp;quot;&amp;gt;SYNCING&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-daily-xp&amp;quot;&amp;gt;TODAY —&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-discovery-row&amp;quot;&amp;gt;DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDividerHtml() {&lt;br /&gt;
        /* 프로필 패널 최신 규칙: 레벨 패널과 버튼 영역 사이에 별도 나눔선은 만들지 않는다. */&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setPanelSync($panel) {&lt;br /&gt;
        if (!$panel || !$panel.length) return;&lt;br /&gt;
&lt;br /&gt;
        $panel.addClass(&#039;is-syncing&#039;).removeClass(&#039;is-max-level&#039;).attr(&#039;data-progress-state&#039;, &#039;syncing&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-title-row&#039;).text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text(&#039;SYNC&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(&#039;— XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(&#039;SYNCING&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-fill&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
        $panel.find(&#039;.progress-xp-gain&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function placePanel($panel) {&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $userBox = $right.children(&#039;.clbi-right-box&#039;).first();&lt;br /&gt;
        if (!$userBox.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $buttonArea = $userBox.children(&#039;.clbi-right-content&#039;).first();&lt;br /&gt;
        var $oldFallback = $panel.closest(&#039;.progress-panel-fallback&#039;);&lt;br /&gt;
&lt;br /&gt;
        if ($buttonArea.length) {&lt;br /&gt;
            var $divider = $(&#039;#profile-progress-divider&#039;);&lt;br /&gt;
&lt;br /&gt;
            $panel.insertBefore($buttonArea);&lt;br /&gt;
&lt;br /&gt;
            if (!$divider.length) {&lt;br /&gt;
                $divider = $(getDividerHtml());&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $divider.insertAfter($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;#profile-progress-divider&#039;).remove();&lt;br /&gt;
            $userBox.append($panel);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if ($oldFallback.length &amp;amp;&amp;amp; !$oldFallback.find(&#039;#progress-panel&#039;).length) {&lt;br /&gt;
            $oldFallback.remove();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensurePanel() {&lt;br /&gt;
        if (!isLoggedIn()) return $();&lt;br /&gt;
&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return $();&lt;br /&gt;
&lt;br /&gt;
        var $panel = $(&#039;#progress-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            $panel = $(getPanelHtml());&lt;br /&gt;
            if (!placePanel($panel)) return $();&lt;br /&gt;
            setPanelSync($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $panel.addClass(&#039;profile-progress-block&#039;);&lt;br /&gt;
            placePanel($panel);&lt;br /&gt;
&lt;br /&gt;
            if (!currentSummary &amp;amp;&amp;amp; $panel.attr(&#039;data-progress-state&#039;) !== &#039;syncing&#039;) {&lt;br /&gt;
                setPanelSync($panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $(&#039;#progress-panel&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clampPercent(value) {&lt;br /&gt;
        return Math.max(0, Math.min(100, value || 0));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasXpNotification(items) {&lt;br /&gt;
        if (!items || !items.length) return false;&lt;br /&gt;
        return items.some(function (item) {&lt;br /&gt;
            return item &amp;amp;&amp;amp; item.type === &#039;xp&#039; &amp;amp;&amp;amp; parseInt(item.amount || 0, 10) &amp;gt; 0;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearBarTimers() {&lt;br /&gt;
        [barTimerA, barTimerB, barTimerC].forEach(function (timer) {&lt;br /&gt;
            if (timer) clearTimeout(timer);&lt;br /&gt;
        });&lt;br /&gt;
        barTimerA = null;&lt;br /&gt;
        barTimerB = null;&lt;br /&gt;
        barTimerC = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBarInstant($fill, $gain, percent) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
        percent = clampPercent(percent);&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: percent + &#039;%&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;none&#039;, left: &#039;0%&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
        $fill.css({ transition: &#039;&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function animateGain($fill, $gain, fromPercent, toPercent, levelChanged) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
&lt;br /&gt;
        fromPercent = clampPercent(fromPercent);&lt;br /&gt;
        toPercent = clampPercent(toPercent);&lt;br /&gt;
&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: fromPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
        if (levelChanged) {&lt;br /&gt;
            var firstDelta = Math.max(0, 100 - fromPercent);&lt;br /&gt;
&lt;br /&gt;
            $gain.css({&lt;br /&gt;
                transition: &#039;none&#039;,&lt;br /&gt;
                opacity: firstDelta &amp;gt; 0 ? 1 : 0,&lt;br /&gt;
                left: fromPercent + &#039;%&#039;,&lt;br /&gt;
                width: firstDelta + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
            barTimerA = setTimeout(function () {&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 540ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: &#039;100%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 260);&lt;br /&gt;
&lt;br /&gt;
            barTimerB = setTimeout(function () {&lt;br /&gt;
                $fill.css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
                $gain.css({ transition: &#039;none&#039;, opacity: toPercent &amp;gt; 0 ? 1 : 0, left: &#039;0%&#039;, width: toPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
                if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 460ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: toPercent + &#039;%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 860);&lt;br /&gt;
&lt;br /&gt;
            barTimerC = setTimeout(function () {&lt;br /&gt;
                $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
            }, 1380);&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var delta = Math.max(0, toPercent - fromPercent);&lt;br /&gt;
&lt;br /&gt;
        if (delta &amp;lt;= 0.15) {&lt;br /&gt;
            setBarInstant($fill, $gain, toPercent);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $gain.css({&lt;br /&gt;
            transition: &#039;none&#039;,&lt;br /&gt;
            opacity: 1,&lt;br /&gt;
            left: fromPercent + &#039;%&#039;,&lt;br /&gt;
            width: delta + &#039;%&#039;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
        barTimerA = setTimeout(function () {&lt;br /&gt;
            $fill.css({&lt;br /&gt;
                transition: &#039;width 560ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                width: toPercent + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }, 260);&lt;br /&gt;
&lt;br /&gt;
        barTimerB = setTimeout(function () {&lt;br /&gt;
            $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
        }, 940);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updatePanel(summary, options) {&lt;br /&gt;
        if (!summary) return;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
&lt;br /&gt;
        var $panel = ensurePanel();&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            pendingSummary = $.extend({}, summary);&lt;br /&gt;
            pendingOptions = $.extend({}, options);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var level = summary.level || 1;&lt;br /&gt;
        var totalXp = summary.totalXp || 0;&lt;br /&gt;
        var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
        var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
        var percent = clampPercent(summary.progressPercent);&lt;br /&gt;
        var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
        var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
        var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
        var title = summary.equippedTitle || summary.title || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $panel.removeClass(&#039;is-syncing&#039;).toggleClass(&#039;is-max-level&#039;, isMaxLevel).attr(&#039;data-progress-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text((isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(totalXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;));&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY &#039; + dailyXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED &#039; + discoveries);&lt;br /&gt;
&lt;br /&gt;
        var $title = $panel.find(&#039;.progress-title-row&#039;);&lt;br /&gt;
        if (title) {&lt;br /&gt;
            $title.text(title).prop(&#039;hidden&#039;, false);&lt;br /&gt;
        } else {&lt;br /&gt;
            $title.text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $fill = $panel.find(&#039;.progress-xp-fill&#039;);&lt;br /&gt;
        var $gain = $panel.find(&#039;.progress-xp-gain&#039;);&lt;br /&gt;
        var animate = !!options.animateGain &amp;amp;&amp;amp; currentSummary &amp;amp;&amp;amp; totalXp &amp;gt; (currentSummary.totalXp || 0);&lt;br /&gt;
&lt;br /&gt;
        if (animate) {&lt;br /&gt;
            animateGain(&lt;br /&gt;
                $fill,&lt;br /&gt;
                $gain,&lt;br /&gt;
                clampPercent(currentSummary.progressPercent),&lt;br /&gt;
                percent,&lt;br /&gt;
                level !== (currentSummary.level || 1)&lt;br /&gt;
            );&lt;br /&gt;
        } else {&lt;br /&gt;
            setBarInstant($fill, $gain, percent);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        currentSummary = $.extend({}, summary);&lt;br /&gt;
        pendingSummary = null;&lt;br /&gt;
        pendingOptions = null;&lt;br /&gt;
        if (summaryRetryTimer) {&lt;br /&gt;
            clearTimeout(summaryRetryTimer);&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
        }&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearSummaryRetry() {&lt;br /&gt;
        if (summaryRetryTimer) clearTimeout(summaryRetryTimer);&lt;br /&gt;
        summaryRetryTimer = null;&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleSummaryRetry(delay) {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRetryTimer) return;&lt;br /&gt;
        if (summaryRetryAttempts &amp;gt;= 12) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRetryAttempts += 1;&lt;br /&gt;
        summaryRetryTimer = setTimeout(function () {&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, delay || 1800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSummary() {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRequested) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRequested = true;&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;progress_summary&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
                if (payload &amp;amp;&amp;amp; payload.available &amp;amp;&amp;amp; payload.summary) {&lt;br /&gt;
                    clearSummaryRetry();&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: false });&lt;br /&gt;
                } else {&lt;br /&gt;
                    scheduleSummaryRetry(2200);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                scheduleSummaryRetry(2200);&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                summaryRequested = false;&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            summaryRequested = false;&lt;br /&gt;
            scheduleSummaryRetry(2200);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queueNotifications(items) {&lt;br /&gt;
        if (!items || !items.length) return;&lt;br /&gt;
&lt;br /&gt;
        items.forEach(function (item) {&lt;br /&gt;
            if (!item) return;&lt;br /&gt;
            notificationQueue.push(item);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        showNextNotification();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function notificationText(item) {&lt;br /&gt;
        if (item.type === &#039;xp&#039;) {&lt;br /&gt;
            return &#039;+&#039; + (item.amount || 0) + &#039; XP · &#039; + (item.label || &#039;문서 열람&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;achievement&#039;) {&lt;br /&gt;
            var xp = item.amount ? &#039; · +&#039; + item.amount + &#039; XP&#039; : &#039;&#039;;&lt;br /&gt;
            return &#039;업적 달성 · &#039; + (item.label || &#039;새 업적&#039;) + xp;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;level&#039;) {&lt;br /&gt;
            return item.label || &#039;레벨 상승&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return item.label || &#039;보상 획득&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showNextNotification() {&lt;br /&gt;
        if (notificationActive) return;&lt;br /&gt;
        if (!notificationQueue.length) return;&lt;br /&gt;
&lt;br /&gt;
        notificationActive = true;&lt;br /&gt;
        var item = notificationQueue.shift();&lt;br /&gt;
        var $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$root.length) {&lt;br /&gt;
            $(&#039;body&#039;).append(&#039;&amp;lt;div id=&amp;quot;progress-toast-root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $toast = $(&#039;&amp;lt;div class=&amp;quot;progress-toast&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        $toast.text(notificationText(item));&lt;br /&gt;
        $root.append($toast);&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            $toast.addClass(&#039;is-visible&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            $toast.removeClass(&#039;is-visible&#039;);&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                $toast.remove();&lt;br /&gt;
                notificationActive = false;&lt;br /&gt;
                showNextNotification();&lt;br /&gt;
            }, 220);&lt;br /&gt;
        }, 2600);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPendingSummaryIfPossible() {&lt;br /&gt;
        if (!pendingSummary) return;&lt;br /&gt;
        updatePanel(pendingSummary, pendingOptions || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handlePageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (!isRewardableClientSide()) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var pageId = getPageId();&lt;br /&gt;
        if (handledPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (inFlightPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inFlightPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;progress_view&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2,&lt;br /&gt;
                errorformat: &#039;plaintext&#039;,&lt;br /&gt;
                pageid: pageId&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_view;&lt;br /&gt;
                if (!payload) return;&lt;br /&gt;
&lt;br /&gt;
                handledPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
                var animate = hasXpNotification(payload.notifications);&lt;br /&gt;
&lt;br /&gt;
                if (payload.summary) {&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: animate });&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (payload.notifications &amp;amp;&amp;amp; payload.notifications.length) {&lt;br /&gt;
                    queueNotifications(payload.notifications);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                inFlightPageIds.delete(pageId);&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            inFlightPageIds.delete(pageId);&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindVisibilitySync() {&lt;br /&gt;
        if (visibilityBound) return;&lt;br /&gt;
        visibilityBound = true;&lt;br /&gt;
&lt;br /&gt;
        document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
            if (document.visibilityState === &#039;visible&#039;) {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bootProgressSystem(reason) {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (isRewardableClientSide()) {&lt;br /&gt;
            handlePageView();&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 350);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 1500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleSpaPageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                handlePageView();&lt;br /&gt;
            }, 80);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySummary(summary, options) {&lt;br /&gt;
        updatePanel(summary, options || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.ProgressSystemWebUi = {&lt;br /&gt;
        boot: bootProgressSystem,&lt;br /&gt;
        requestSummary: requestSummary,&lt;br /&gt;
        applySummary: applySummary,&lt;br /&gt;
        handlePageView: handlePageView,&lt;br /&gt;
        handleSpaPageView: handleSpaPageView,&lt;br /&gt;
        ensurePanel: ensurePanel&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        bindVisibilitySync();&lt;br /&gt;
        bootProgressSystem(&#039;documentReady&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* CLBI Nations / Historical Events year tabs&lt;br /&gt;
 * Mirrors the country information panel model:&lt;br /&gt;
 * active tab uses .is-active/aria-selected and inactive pages use hidden.&lt;br /&gt;
 */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function activateClbiNationsHistoryYear(panel, targetYear) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var pages;&lt;br /&gt;
&lt;br /&gt;
        if (!panel || !targetYear) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        pages = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-page[data-year-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
        if (!tabs.length || !pages.length) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (tab) {&lt;br /&gt;
            var active = tab.getAttribute(&#039;data-year&#039;) === targetYear;&lt;br /&gt;
            tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
            tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        pages.forEach(function (page) {&lt;br /&gt;
            var active = page.getAttribute(&#039;data-year-panel&#039;) === targetYear;&lt;br /&gt;
            page.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
            if (active) {&lt;br /&gt;
                page.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
            } else {&lt;br /&gt;
                page.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveClbiNationsHistoryYear(panel, direction) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var activeIndex;&lt;br /&gt;
        var nextIndex;&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!panel) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
        activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
            return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
        nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
        target = tabs[nextIndex].getAttribute(&#039;data-year&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (activateClbiNationsHistoryYear(panel, target)) {&lt;br /&gt;
            tabs[nextIndex].focus();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initClbiNationsHistoryYearTabs(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var panels = scope.querySelectorAll(&#039;.clbi-nations-history-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(panels, function (panel) {&lt;br /&gt;
            if (panel.getAttribute(&#039;data-clbi-history-tabs-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
            panel.setAttribute(&#039;data-clbi-history-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (activateClbiNationsHistoryYear(panel, tab.getAttribute(&#039;data-year&#039;))) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
                var handled = false;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsHistoryYear(panel, -1);&lt;br /&gt;
                else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsHistoryYear(panel, 1);&lt;br /&gt;
                else if (event.key === &#039;Home&#039;) handled = activateClbiNationsHistoryYear(panel, (panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;) || {}).getAttribute &amp;amp;&amp;amp; panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;).getAttribute(&#039;data-year&#039;));&lt;br /&gt;
                else if (event.key === &#039;End&#039;) {&lt;br /&gt;
                    var tabs = panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;);&lt;br /&gt;
                    var last = tabs[tabs.length - 1];&lt;br /&gt;
                    handled = last ? activateClbiNationsHistoryYear(panel, last.getAttribute(&#039;data-year&#039;)) : false;&lt;br /&gt;
                    if (handled) last.focus();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (handled) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                    event.stopPropagation();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.initClbiNationsHistoryYearTabs = initClbiNationsHistoryYearTabs;&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initClbiNationsHistoryYearTabs(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initClbiNationsHistoryYearTabs($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Decoration runtime renderer&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var REGISTRY_TITLE = &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
    var RENDERED_ATTR = &#039;data-wiki-decoration-rendered&#039;;&lt;br /&gt;
    var HOST_ATTR = &#039;data-wiki-decoration-host&#039;;&lt;br /&gt;
    var MAIN_PAGE_PLACEMENT = &#039;main-body-well&#039;;&lt;br /&gt;
    var MAIN_PAGE_TARGET = &#039;.main-portal .main-body-well&#039;;&lt;br /&gt;
    var runtimeToken = 0;&lt;br /&gt;
    var lastRegistry = null;&lt;br /&gt;
    var lastRenderedPageKey = &#039;&#039;;&lt;br /&gt;
    var scheduledRender = 0;&lt;br /&gt;
    var nationsPlacementObserver = null;&lt;br /&gt;
    var observedNationsStack = null;&lt;br /&gt;
    var mainPagePlacementObserver = null;&lt;br /&gt;
    var mainPagePlacementMutationObserver = null;&lt;br /&gt;
    var observedMainPagePortal = null;&lt;br /&gt;
    var scheduledMainPageRebase = 0;&lt;br /&gt;
    var pixelAssetCache = {};&lt;br /&gt;
    var pixelCanvasCache = {};&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentPageKey() {&lt;br /&gt;
        var raw = mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        return normalizePageName(raw) || raw || &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureMainPageBodyWell() {&lt;br /&gt;
        var portal;&lt;br /&gt;
        var topMount;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var well;&lt;br /&gt;
        var manifesto;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
        portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        if (!portal) return null;&lt;br /&gt;
&lt;br /&gt;
        topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) {&lt;br /&gt;
                    topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                } else {&lt;br /&gt;
                    topMount.parentNode.appendChild(panel);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) {&lt;br /&gt;
            well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return well;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function cssAttrEscape(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).replace(/\\/g, &#039;\\\\&#039;).replace(/&amp;quot;/g, &#039;\\&amp;quot;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEra() {&lt;br /&gt;
        var content = document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var globe;&lt;br /&gt;
&lt;br /&gt;
        if (content) return content.getAttribute(&#039;data-era-content&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.querySelector(&#039;.clbi-nations-era-title-plate.is-active[data-era]&#039;);&lt;br /&gt;
        if (title) return title.getAttribute(&#039;data-era&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe]&#039;);&lt;br /&gt;
        if (globe) {&lt;br /&gt;
            return globe.getAttribute(&#039;data-current-era&#039;) || globe.getAttribute(&#039;data-nations-current-era&#039;) || globe.getAttribute(&#039;data-era-year&#039;) || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEraPanel() {&lt;br /&gt;
        var era = getActiveNationsEra();&lt;br /&gt;
        var selector;&lt;br /&gt;
        if (!era) return document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        selector = &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;]&#039;;&lt;br /&gt;
        return document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsContinent() {&lt;br /&gt;
        var eraPanel = getActiveNationsEraPanel();&lt;br /&gt;
        var root = eraPanel || document;&lt;br /&gt;
        var tab = root.querySelector(&#039;.clbi-nations-tabpanel-tab.is-active[data-continent], .clbi-nations-tabpanel-tab[aria-selected=&amp;quot;true&amp;quot;][data-continent]&#039;);&lt;br /&gt;
        var panel;&lt;br /&gt;
&lt;br /&gt;
        if (tab) return tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        panel = root.querySelector(&#039;.clbi-nations-tabpanel-continent.is-active[data-continent-panel]&#039;);&lt;br /&gt;
        if (panel) return panel.getAttribute(&#039;data-continent-panel&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationNationsBodySelector(era) {&lt;br /&gt;
        if (era) {&lt;br /&gt;
            return &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;] .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden]) .clbi-nations-tabpanel-body, .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Decoration semantic placement resolver&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    장식 저장 데이터의 placement 값은 &amp;quot;사용자가 고른 의미상 위치&amp;quot;를 나타낸다.&lt;br /&gt;
    resolver는 그 의미값을 실제 DOM 부착 위치와 표시 조건으로 번역한다.&lt;br /&gt;
&lt;br /&gt;
    예: 시대 문서에서 사용자가 1950년 / 아메리카를 지정하면 의미상 scope는&lt;br /&gt;
    그 조합이지만, 이미지를 붙일 기준면은 대륙 패널 자체가 아니라&lt;br /&gt;
    .clbi-nations-tabpanel-body이다. 따라서 placement=nations-continent-body는&lt;br /&gt;
    .clbi-nations-tabpanel-body에 이미지를 붙이고, 현재 활성 연도와 대륙이 저장값과&lt;br /&gt;
    일치할 때만 렌더링한다.&lt;br /&gt;
&lt;br /&gt;
    유지보수 규칙:&lt;br /&gt;
    - 새 조합형 문서가 생기면 entry.target을 매번 특수하게 저장하지 말고 placement를 추가한다.&lt;br /&gt;
    - DevTools.js 에디터 미리보기와 Common.js 런타임 렌더러의 resolver는 같은 의미를 가져야 한다.&lt;br /&gt;
    - target은 물리적 기준면, era/continent 같은 필드는 표시 조건으로 다룬다.&lt;br /&gt;
    */&lt;br /&gt;
    function shouldUseNationsBodyPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var target = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!document.querySelector(&#039;.clbi-nations-panel-stack&#039;)) return false;&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) return true;&lt;br /&gt;
        if (era || continent) return true;&lt;br /&gt;
        if (target.indexOf(&#039;clbi-nations-tabpanel-continent&#039;) !== -1) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveDecorationPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() || &#039;.liberty-content-main&#039;;&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!placement &amp;amp;&amp;amp; shouldUseNationsBodyPlacement(entry)) {&lt;br /&gt;
            placement = &#039;nations-continent-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;boot-gate&#039; || placement === &#039;loading-screen&#039;) {&lt;br /&gt;
            selector = &#039;#boot-gate-screen .boot-gate-decoration-layer, #boot-gate-screen&#039;;&lt;br /&gt;
            target = document.querySelector(selector);&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: !!document.getElementById(&#039;boot-gate-screen&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) {&lt;br /&gt;
            selector = getDecorationNationsBodySelector(era);&lt;br /&gt;
            target = document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-tabpanel-body&#039;) || document.querySelector(String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim());&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: (!era || era === getActiveNationsEra()) &amp;amp;&amp;amp; (!continent || continent === getActiveNationsContinent())&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문의 일반 장식은 저장된 target과 관계없이 본문 우물 안에서 렌더링한다.&lt;br /&gt;
        우물이 overflow:hidden이므로 #1d1d1d 프레임 밖으로 나갈 수 없다.&lt;br /&gt;
        boot/loading 및 국가 패널 전용 placement는 위 분기에서 기존 동작을 유지한다.&lt;br /&gt;
        */&lt;br /&gt;
        var mainPageWell = ensureMainPageBodyWell();&lt;br /&gt;
        if (mainPageWell) {&lt;br /&gt;
            return {&lt;br /&gt;
                placement: MAIN_PAGE_PLACEMENT,&lt;br /&gt;
                target: mainPageWell,&lt;br /&gt;
                targetSelector: MAIN_PAGE_TARGET,&lt;br /&gt;
                visible: true&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            placement: placement,&lt;br /&gt;
            target: document.querySelector(selector),&lt;br /&gt;
            targetSelector: selector,&lt;br /&gt;
            visible: true&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeNumber(value, fallback) {&lt;br /&gt;
        var n = parseFloat(value);&lt;br /&gt;
        return Number.isFinite(n) ? n : fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBool(value, fallback) {&lt;br /&gt;
        if (value === true || value === &#039;true&#039; || value === &#039;1&#039; || value === 1) return true;&lt;br /&gt;
        if (value === false || value === &#039;false&#039; || value === &#039;0&#039; || value === 0) return false;&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeSrc(src) {&lt;br /&gt;
        src = String(src || &#039;&#039;).trim();&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(src) || src.charAt(0) === &#039;/&#039; || src.indexOf(&#039;data:&#039;) === 0 || src.indexOf(&#039;blob:&#039;) === 0) return src;&lt;br /&gt;
        return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(src);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function normalizeAssetType(entry) {&lt;br /&gt;
        var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var ref = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (type === &#039;clbi-pixel-json&#039; || type === &#039;pixel-rle&#039; || type === &#039;pixel-json&#039;) return &#039;pixel-json&#039;;&lt;br /&gt;
        if (!type &amp;amp;&amp;amp; /\.json(?:[?#].*)?$/i.test(ref)) return &#039;pixel-json&#039;;&lt;br /&gt;
        return type || &#039;image&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPixelJsonDecoration(entry) {&lt;br /&gt;
        return normalizeAssetType(entry) === &#039;pixel-json&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPixelJsonRef(entry) {&lt;br /&gt;
        return String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelJsonUrl(ref) {&lt;br /&gt;
        ref = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        if (!ref) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(ref) || ref.charAt(0) === &#039;/&#039; || ref.indexOf(&#039;data:&#039;) === 0 || ref.indexOf(&#039;blob:&#039;) === 0) return ref;&lt;br /&gt;
        if (/^(?:file|파일):/i.test(ref)) return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(ref.replace(/^(?:file|파일):/i, &#039;&#039;));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            if (ref.indexOf(&#039;:&#039;) !== -1) {&lt;br /&gt;
                return mw.util.getUrl(ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
            }&lt;br /&gt;
            return mw.util.getUrl(&#039;MediaWiki:&#039; + ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return ref;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function parsePixelColor(value) {&lt;br /&gt;
        var text;&lt;br /&gt;
        var m;&lt;br /&gt;
        if (Array.isArray(value)) {&lt;br /&gt;
            return [&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[0]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[1]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[2]) || 0)),&lt;br /&gt;
                value.length &amp;gt; 3 ? Math.max(0, Math.min(255, Number(value[3]) || 0)) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        m = text.match(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i);&lt;br /&gt;
        if (!m) return [0, 0, 0, 0];&lt;br /&gt;
        text = m[1];&lt;br /&gt;
        if (text.length === 3 || text.length === 4) {&lt;br /&gt;
            return [&lt;br /&gt;
                parseInt(text.charAt(0) + text.charAt(0), 16),&lt;br /&gt;
                parseInt(text.charAt(1) + text.charAt(1), 16),&lt;br /&gt;
                parseInt(text.charAt(2) + text.charAt(2), 16),&lt;br /&gt;
                text.length === 4 ? parseInt(text.charAt(3) + text.charAt(3), 16) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        return [&lt;br /&gt;
            parseInt(text.slice(0, 2), 16),&lt;br /&gt;
            parseInt(text.slice(2, 4), 16),&lt;br /&gt;
            parseInt(text.slice(4, 6), 16),&lt;br /&gt;
            text.length === 8 ? parseInt(text.slice(6, 8), 16) : 255&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function decodePixelRle36(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var parts;&lt;br /&gt;
        var runs = [];&lt;br /&gt;
        var i;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
&lt;br /&gt;
        if (!text) return runs;&lt;br /&gt;
        parts = text.split(&#039;,&#039;);&lt;br /&gt;
        for (i = 0; i + 3 &amp;lt; parts.length; i += 4) {&lt;br /&gt;
            x = parseInt(parts[i], 36);&lt;br /&gt;
            y = parseInt(parts[i + 1], 36);&lt;br /&gt;
            len = parseInt(parts[i + 2], 36);&lt;br /&gt;
            colorIndex = parseInt(parts[i + 3], 36);&lt;br /&gt;
            if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(len) || !Number.isFinite(colorIndex)) continue;&lt;br /&gt;
            runs.push([x, y, len, colorIndex]);&lt;br /&gt;
        }&lt;br /&gt;
        return runs;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelAsset(doc) {&lt;br /&gt;
        var encoding = String(doc &amp;amp;&amp;amp; (doc.encoding || doc.e) || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var width = Math.round(Number(doc &amp;amp;&amp;amp; (doc.width || doc.w)) || 0);&lt;br /&gt;
        var height = Math.round(Number(doc &amp;amp;&amp;amp; (doc.height || doc.h)) || 0);&lt;br /&gt;
        var paletteSource = Array.isArray(doc &amp;amp;&amp;amp; doc.palette) ? doc.palette : (Array.isArray(doc &amp;amp;&amp;amp; doc.p) ? doc.p : []);&lt;br /&gt;
        var palette = paletteSource.map(parsePixelColor);&lt;br /&gt;
        var runs;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        CLBI Pixel Forge v0.2.3 compact format:&lt;br /&gt;
        - MediaWiki 단일 문서 크기 제한을 피하기 위해 사람이 읽기 쉬운 [[x,y,len,c], ...] 배열 대신&lt;br /&gt;
          base36 토큰 문자열을 쓴다.&lt;br /&gt;
        - 형식은 x,y,len,colorIndex를 4개 토큰 단위로 반복한 rle36이다.&lt;br /&gt;
        - 복원 후 좌표계는 기존 runs 배열과 완전히 같으며, 최종 CSS px에 1:1로 찍는다.&lt;br /&gt;
        */&lt;br /&gt;
        if ((encoding === &#039;rle36&#039; || encoding === &#039;clbi-rle36&#039;) &amp;amp;&amp;amp; typeof (doc &amp;amp;&amp;amp; doc.r) === &#039;string&#039;) {&lt;br /&gt;
            runs = decodePixelRle36(doc.r);&lt;br /&gt;
        } else {&lt;br /&gt;
            runs = Array.isArray(doc &amp;amp;&amp;amp; doc.runs) ? doc.runs : [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!width || !height || width &amp;lt; 1 || height &amp;lt; 1) throw new Error(&#039;invalid pixel decoration size&#039;);&lt;br /&gt;
        if (width &amp;gt; 8192 || height &amp;gt; 8192) throw new Error(&#039;pixel decoration too large&#039;);&lt;br /&gt;
        return {&lt;br /&gt;
            type: String(doc &amp;amp;&amp;amp; (doc.type || doc.t) || &#039;clbi-pixel-decoration&#039;),&lt;br /&gt;
            version: Number(doc &amp;amp;&amp;amp; (doc.version || doc.v)) || 1,&lt;br /&gt;
            encoding: encoding || &#039;runs&#039;,&lt;br /&gt;
            width: width,&lt;br /&gt;
            height: height,&lt;br /&gt;
            palette: palette,&lt;br /&gt;
            runs: runs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchPixelAsset(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelAssetCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {&lt;br /&gt;
            promise: fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
                .then(function (response) {&lt;br /&gt;
                    if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                    return response.json();&lt;br /&gt;
                })&lt;br /&gt;
                .then(normalizePixelAsset)&lt;br /&gt;
        };&lt;br /&gt;
        pixelAssetCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawPixelAssetToCanvas(canvas, asset) {&lt;br /&gt;
        var ctx;&lt;br /&gt;
        var image;&lt;br /&gt;
        var data;&lt;br /&gt;
        var i;&lt;br /&gt;
        var run;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var color;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
        var p;&lt;br /&gt;
        var k;&lt;br /&gt;
&lt;br /&gt;
        canvas.width = asset.width;&lt;br /&gt;
        canvas.height = asset.height;&lt;br /&gt;
        ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        ctx.imageSmoothingEnabled = false;&lt;br /&gt;
        image = ctx.createImageData(asset.width, asset.height);&lt;br /&gt;
        data = image.data;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; asset.runs.length; i += 1) {&lt;br /&gt;
            run = asset.runs[i];&lt;br /&gt;
            if (!Array.isArray(run) || run.length &amp;lt; 4) continue;&lt;br /&gt;
            x = Math.round(Number(run[0]) || 0);&lt;br /&gt;
            y = Math.round(Number(run[1]) || 0);&lt;br /&gt;
            len = Math.round(Number(run[2]) || 0);&lt;br /&gt;
            colorIndex = Math.round(Number(run[3]) || 0);&lt;br /&gt;
            color = asset.palette[colorIndex];&lt;br /&gt;
            if (!color || len &amp;lt;= 0 || y &amp;lt; 0 || y &amp;gt;= asset.height || x &amp;gt;= asset.width) continue;&lt;br /&gt;
            if (x &amp;lt; 0) {&lt;br /&gt;
                len += x;&lt;br /&gt;
                x = 0;&lt;br /&gt;
            }&lt;br /&gt;
            len = Math.min(len, asset.width - x);&lt;br /&gt;
            for (k = 0; k &amp;lt; len; k += 1) {&lt;br /&gt;
                p = ((y * asset.width) + x + k) * 4;&lt;br /&gt;
                data[p] = color[0];&lt;br /&gt;
                data[p + 1] = color[1];&lt;br /&gt;
                data[p + 2] = color[2];&lt;br /&gt;
                data[p + 3] = color[3];&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preparePixelCanvas(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelCanvasCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {};&lt;br /&gt;
        cached.promise = fetchPixelAsset(ref).then(function (asset) {&lt;br /&gt;
            var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            drawPixelAssetToCanvas(canvas, asset);&lt;br /&gt;
            canvas.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;data-decoration-asset-size&#039;, asset.width + &#039;x&#039; + asset.height);&lt;br /&gt;
            cached.canvas = canvas;&lt;br /&gt;
            cached.width = asset.width;&lt;br /&gt;
            cached.height = asset.height;&lt;br /&gt;
            return canvas;&lt;br /&gt;
        });&lt;br /&gt;
        pixelCanvasCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedPixelCanvasSync(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached = url ? pixelCanvasCache[url] : null;&lt;br /&gt;
        return cached &amp;amp;&amp;amp; cached.canvas ? cached.canvas : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queryDecorationTarget(selector) {&lt;br /&gt;
        selector = String(selector || &#039;&#039;).trim();&lt;br /&gt;
        if (!selector) return null;&lt;br /&gt;
        try {&lt;br /&gt;
            return document.querySelector(selector);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function usesCanonicalMainPageCoordinates(entry, actualTarget) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var declaredTarget;&lt;br /&gt;
&lt;br /&gt;
        if (placement === MAIN_PAGE_PLACEMENT) return true;&lt;br /&gt;
        if (!actualTarget || !selector) return false;&lt;br /&gt;
&lt;br /&gt;
        declaredTarget = queryDecorationTarget(selector);&lt;br /&gt;
        return declaredTarget === actualTarget;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationLocalPosition(entry, actualTarget) {&lt;br /&gt;
        var x = normalizeNumber(entry &amp;amp;&amp;amp; entry.x, 0);&lt;br /&gt;
        var y = normalizeNumber(entry &amp;amp;&amp;amp; entry.y, 0);&lt;br /&gt;
        var sourceX = x;&lt;br /&gt;
        var sourceY = y;&lt;br /&gt;
        var sourceSelector;&lt;br /&gt;
        var sourceTarget;&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var actualRect;&lt;br /&gt;
        var sourceOriginX;&lt;br /&gt;
        var sourceOriginY;&lt;br /&gt;
        var actualOriginX;&lt;br /&gt;
        var actualOriginY;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문 데코 좌표 보존&lt;br /&gt;
        -----------------------------------------&lt;br /&gt;
        저장된 x/y는 기존 entry.target 좌표계를 기준으로 만들어졌다.&lt;br /&gt;
        데코를 main-body-well 안으로 옮길 때 x/y를 그대로 적용하면&lt;br /&gt;
        더 아래에서 시작하는 새 좌표계 때문에 장식이 바닥으로 밀린다.&lt;br /&gt;
&lt;br /&gt;
        실제 화면상의 위치는 유지하고 부모만 우물로 바꾼다.&lt;br /&gt;
        기존 기준면의 padding-box 원점과 새 우물의 padding-box 원점 차이를&lt;br /&gt;
        정수 px로 더해 새 로컬 좌표를 만든다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            currentPageKey() === &#039;대문&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList.contains(&#039;main-body-well&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
            !usesCanonicalMainPageCoordinates(entry, actualTarget)&lt;br /&gt;
        ) {&lt;br /&gt;
            sourceSelector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() ||&lt;br /&gt;
                &#039;.liberty-content-main&#039;;&lt;br /&gt;
            sourceTarget = queryDecorationTarget(sourceSelector) ||&lt;br /&gt;
                document.querySelector(&#039;.liberty-content-main&#039;) ||&lt;br /&gt;
                document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (sourceTarget &amp;amp;&amp;amp; sourceTarget !== actualTarget) {&lt;br /&gt;
                sourceRect = sourceTarget.getBoundingClientRect();&lt;br /&gt;
                actualRect = actualTarget.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
                sourceOriginX = sourceRect.left + normalizeNumber(sourceTarget.clientLeft, 0);&lt;br /&gt;
                sourceOriginY = sourceRect.top + normalizeNumber(sourceTarget.clientTop, 0);&lt;br /&gt;
                actualOriginX = actualRect.left + normalizeNumber(actualTarget.clientLeft, 0);&lt;br /&gt;
                actualOriginY = actualRect.top + normalizeNumber(actualTarget.clientTop, 0);&lt;br /&gt;
&lt;br /&gt;
                x += Math.round(sourceOriginX - actualOriginX);&lt;br /&gt;
                y += Math.round(sourceOriginY - actualOriginY);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            x: Math.round(x),&lt;br /&gt;
            y: Math.round(y),&lt;br /&gt;
            sourceX: Math.round(sourceX),&lt;br /&gt;
            sourceY: Math.round(sourceY),&lt;br /&gt;
            sourceSelector: sourceSelector || &#039;&#039;&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecorationBaseStyle(node, entry, actualTarget) {&lt;br /&gt;
        var position = getDecorationLocalPosition(entry, actualTarget);&lt;br /&gt;
&lt;br /&gt;
        node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
        node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
        node.style.opacity = String(normalizeNumber(entry.opacity, 1));&lt;br /&gt;
        node.style.zIndex = String(Math.round(normalizeNumber(entry.zIndex, 0)));&lt;br /&gt;
        node.style.pointerEvents = String(entry.pointerEvents || &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (position.sourceSelector) {&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-coordinate-source&#039;, position.sourceSelector);&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-x&#039;, String(position.sourceX));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-y&#039;, String(position.sourceY));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (entry.blendMode) node.style.mixBlendMode = String(entry.blendMode);&lt;br /&gt;
        if (entry.filter) node.style.filter = String(entry.filter);&lt;br /&gt;
        if (entry.transform) node.style.transform = String(entry.transform);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDecorationNodeActiveForNationsState(node) {&lt;br /&gt;
        var era = node ? String(node.getAttribute(&#039;data-decoration-era&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        var continent = node ? String(node.getAttribute(&#039;data-decoration-continent&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; era !== getActiveNationsEra()) return false;&lt;br /&gt;
        if (continent &amp;amp;&amp;amp; continent !== getActiveNationsContinent()) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDecorationNodeVisibility(node, visible) {&lt;br /&gt;
        if (!node) return;&lt;br /&gt;
        visible = visible !== false;&lt;br /&gt;
        /* aria-hidden stays true because wiki decorations are purely visual. */&lt;br /&gt;
        if (node.hidden !== !visible) node.hidden = !visible;&lt;br /&gt;
        if (node.style.display !== (visible ? &#039;&#039; : &#039;none&#039;)) node.style.display = visible ? &#039;&#039; : &#039;none&#039;;&lt;br /&gt;
        node.setAttribute(&#039;data-decoration-visible&#039;, visible ? &#039;1&#039; : &#039;0&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateDecorationVisibility(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var count = 0;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            setDecorationNodeVisibility(node, isDecorationNodeActiveForNationsState(node));&lt;br /&gt;
            count += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return count;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasRenderedDecorations(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return !!(scope &amp;amp;&amp;amp; scope.querySelector &amp;amp;&amp;amp; scope.querySelector(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPixelJsonDecoration(entry, target, visible) {&lt;br /&gt;
        var ref = getPixelJsonRef(entry);&lt;br /&gt;
        var template;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var ctx;&lt;br /&gt;
        if (!ref) return false;&lt;br /&gt;
&lt;br /&gt;
        function makeCanvasFromTemplate(source) {&lt;br /&gt;
            var out = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            var outCtx;&lt;br /&gt;
            out.className = &#039;wiki-decoration wiki-decoration-pixel-json&#039;;&lt;br /&gt;
            out.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
            out.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-type&#039;, &#039;pixel-json&#039;);&lt;br /&gt;
            if (entry.placement) out.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
            if (entry.era) out.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
            if (entry.continent) out.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
            out.width = source.width;&lt;br /&gt;
            out.height = source.height;&lt;br /&gt;
            out.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            out.style.width = source.width + &#039;px&#039;;&lt;br /&gt;
            out.style.height = source.height + &#039;px&#039;;&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-size&#039;, source.width + &#039;x&#039; + source.height);&lt;br /&gt;
            outCtx = out.getContext(&#039;2d&#039;);&lt;br /&gt;
            outCtx.imageSmoothingEnabled = false;&lt;br /&gt;
            outCtx.drawImage(source, 0, 0);&lt;br /&gt;
            applyDecorationBaseStyle(out, entry, target);&lt;br /&gt;
            setDecorationNodeVisibility(out, visible);&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template = getPreparedPixelCanvasSync(ref);&lt;br /&gt;
        if (template) {&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(template));&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Fallback path only.  A full entry pack should prepare the template before the&lt;br /&gt;
           normal UI is released, so users should not see a blank decoration canvas. */&lt;br /&gt;
        preparePixelCanvas(ref).then(function (source) {&lt;br /&gt;
            if (!target || !target.parentNode || !source) return;&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(source));&lt;br /&gt;
        }).catch(function () {});&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preloadPixelAssetsForRegistry(registry) {&lt;br /&gt;
        var promises = [];&lt;br /&gt;
        decorationList(registry).forEach(function (entry) {&lt;br /&gt;
            var ref;&lt;br /&gt;
            if (!entry || !matchesPage(entry) || !isPixelJsonDecoration(entry)) return;&lt;br /&gt;
            ref = getPixelJsonRef(entry);&lt;br /&gt;
            if (!ref) return;&lt;br /&gt;
            promises.push(preparePixelCanvas(ref).catch(function () { return null; }));&lt;br /&gt;
        });&lt;br /&gt;
        return Promise.all(promises);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decorationList(registry) {&lt;br /&gt;
        if (!registry || typeof registry !== &#039;object&#039;) return [];&lt;br /&gt;
        if (Array.isArray(registry)) return registry;&lt;br /&gt;
        if (Array.isArray(registry.decorations)) return registry.decorations;&lt;br /&gt;
        return [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesPage(entry) {&lt;br /&gt;
        var page = currentPageKey();&lt;br /&gt;
        var underscored = page.replace(/ /g, &#039;_&#039;);&lt;br /&gt;
        var pages = entry &amp;amp;&amp;amp; entry.pages;&lt;br /&gt;
        var target = entry &amp;amp;&amp;amp; entry.page;&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        if (normalizeBool(entry &amp;amp;&amp;amp; entry.global, false)) return true;&lt;br /&gt;
        if (String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;boot-gate&#039; || String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;loading-screen&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (normalizePageName(target).toLowerCase() === &#039;__boot__&#039; || normalizePageName(target).toLowerCase() === &#039;loading-screen&#039;) return true;&lt;br /&gt;
        if (!target &amp;amp;&amp;amp; !pages) return true;&lt;br /&gt;
&lt;br /&gt;
        if (Array.isArray(pages)) {&lt;br /&gt;
            for (i = 0; i &amp;lt; pages.length; i += 1) {&lt;br /&gt;
                if (normalizePageName(pages[i]) === page || String(pages[i] || &#039;&#039;) === underscored) return true;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        target = normalizePageName(target);&lt;br /&gt;
        return target === page || target === underscored;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearRendered(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureHost(target) {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (!target) return;&lt;br /&gt;
        target.setAttribute(HOST_ATTR, &#039;1&#039;);&lt;br /&gt;
        style = window.getComputedStyle ? window.getComputedStyle(target) : null;&lt;br /&gt;
        if (style &amp;amp;&amp;amp; style.position === &#039;static&#039;) target.style.position = &#039;relative&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecoration(entry) {&lt;br /&gt;
        var resolved;&lt;br /&gt;
        var target;&lt;br /&gt;
        var src;&lt;br /&gt;
        var img;&lt;br /&gt;
        var width;&lt;br /&gt;
        var height;&lt;br /&gt;
&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039; || normalizeBool(entry.enabled, true) === false) return false;&lt;br /&gt;
        resolved = resolveDecorationPlacement(entry);&lt;br /&gt;
        if (!resolved) return false;&lt;br /&gt;
        target = resolved.target;&lt;br /&gt;
        if (!target) return false;&lt;br /&gt;
&lt;br /&gt;
        ensureHost(target);&lt;br /&gt;
&lt;br /&gt;
        if (isPixelJsonDecoration(entry)) {&lt;br /&gt;
            return applyPixelJsonDecoration(entry, target, resolved.visible !== false);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        src = normalizeSrc(entry.src);&lt;br /&gt;
        if (!src) return false;&lt;br /&gt;
&lt;br /&gt;
        img = document.createElement(&#039;img&#039;);&lt;br /&gt;
        img.className = &#039;wiki-decoration&#039;;&lt;br /&gt;
        img.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
        img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, &#039;&#039;);&lt;br /&gt;
        img.setAttribute(&#039;decoding&#039;, &#039;async&#039;);&lt;br /&gt;
        img.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        img.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
        if (entry.placement) img.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
        if (entry.era) img.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
        if (entry.continent) img.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
        img.src = src;&lt;br /&gt;
&lt;br /&gt;
        applyDecorationBaseStyle(img, entry, target);&lt;br /&gt;
        width = normalizeNumber(entry.width, NaN);&lt;br /&gt;
        height = normalizeNumber(entry.height, NaN);&lt;br /&gt;
        if (Number.isFinite(width) &amp;amp;&amp;amp; width &amp;gt; 0) img.style.width = width + &#039;px&#039;;&lt;br /&gt;
        if (Number.isFinite(height) &amp;amp;&amp;amp; height &amp;gt; 0) img.style.height = height + &#039;px&#039;;&lt;br /&gt;
        if (entry.objectFit) img.style.objectFit = String(entry.objectFit);&lt;br /&gt;
        setDecorationNodeVisibility(img, resolved.visible !== false);&lt;br /&gt;
&lt;br /&gt;
        target.appendChild(img);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mainPageLayoutSignature() {&lt;br /&gt;
        var source = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
        var well = ensureMainPageBodyWell();&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var wellRect;&lt;br /&gt;
&lt;br /&gt;
        if (!source || !well) return &#039;&#039;;&lt;br /&gt;
        sourceRect = source.getBoundingClientRect();&lt;br /&gt;
        wellRect = well.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            Math.round((wellRect.left + normalizeNumber(well.clientLeft, 0)) - (sourceRect.left + normalizeNumber(source.clientLeft, 0))),&lt;br /&gt;
            Math.round((wellRect.top + normalizeNumber(well.clientTop, 0)) - (sourceRect.top + normalizeNumber(source.clientTop, 0))),&lt;br /&gt;
            Math.round(wellRect.width),&lt;br /&gt;
            Math.round(wellRect.height)&lt;br /&gt;
        ].join(&#039;|&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMainPageDecorationLayoutReady() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var panel = portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var topMount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!portal || !panel || !ensureMainPageBodyWell()) return false;&lt;br /&gt;
        if (!document.body || !document.body.classList.contains(&#039;clbi-main-page&#039;)) return false;&lt;br /&gt;
        if (topMount &amp;amp;&amp;amp; !topMount.querySelector(&#039;.portal-category-nav&#039;)) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForMainPageDecorationLayout() {&lt;br /&gt;
        var started;&lt;br /&gt;
        var previous = &#039;&#039;;&lt;br /&gt;
        var stableFrames = 0;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return Promise.resolve(true);&lt;br /&gt;
        started = Date.now();&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function check() {&lt;br /&gt;
                var signature = mainPageLayoutSignature();&lt;br /&gt;
&lt;br /&gt;
                if (signature &amp;amp;&amp;amp; signature === previous) stableFrames += 1;&lt;br /&gt;
                else stableFrames = 0;&lt;br /&gt;
                previous = signature;&lt;br /&gt;
&lt;br /&gt;
                if ((isMainPageDecorationLayoutReady() &amp;amp;&amp;amp; stableFrames &amp;gt;= 2) || Date.now() - started &amp;gt;= 1200) {&lt;br /&gt;
                    resolve(true);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (window.requestAnimationFrame) window.requestAnimationFrame(check);&lt;br /&gt;
                else window.setTimeout(check, 16);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebaseLegacyMainPageDecorations() {&lt;br /&gt;
        var well;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return;&lt;br /&gt;
        well = ensureMainPageBodyWell();&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-coordinate-source]&#039;).forEach(function (node) {&lt;br /&gt;
            var position = getDecorationLocalPosition({&lt;br /&gt;
                target: node.getAttribute(&#039;data-decoration-coordinate-source&#039;) || &#039;.liberty-content-main&#039;,&lt;br /&gt;
                x: normalizeNumber(node.getAttribute(&#039;data-decoration-source-x&#039;), 0),&lt;br /&gt;
                y: normalizeNumber(node.getAttribute(&#039;data-decoration-source-y&#039;), 0)&lt;br /&gt;
            }, well);&lt;br /&gt;
&lt;br /&gt;
            node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
            node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleMainPageDecorationRebase() {&lt;br /&gt;
        if (scheduledMainPageRebase) return;&lt;br /&gt;
        scheduledMainPageRebase = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindMainPagePlacementRefresh() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var nodes;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039; || !portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = null;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (observedMainPagePortal !== portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = portal;&lt;br /&gt;
&lt;br /&gt;
            if (typeof ResizeObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementObserver = new ResizeObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                nodes = [&lt;br /&gt;
                    document.querySelector(&#039;.liberty-content-main&#039;),&lt;br /&gt;
                    portal,&lt;br /&gt;
                    portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-panel&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-well&#039;)&lt;br /&gt;
                ];&lt;br /&gt;
                nodes.forEach(function (node) {&lt;br /&gt;
                    if (node) mainPagePlacementObserver.observe(node);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementMutationObserver = new MutationObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                mainPagePlacementMutationObserver.observe(portal, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        scheduleMainPageDecorationRebase();&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 80);&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderForCurrentLayout(registry, token) {&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return Promise.resolve(registry);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return waitForMainPageDecorationLayout().then(function () {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return registry;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(registry) {&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        lastRegistry = registry || { decorations: [] };&lt;br /&gt;
        lastRenderedPageKey = currentPageKey();&lt;br /&gt;
        bindNationsPlacementRefresh();&lt;br /&gt;
        clearRendered(document);&lt;br /&gt;
        decorationList(lastRegistry).forEach(function (entry) {&lt;br /&gt;
            if (token === runtimeToken &amp;amp;&amp;amp; matchesPage(entry)) applyDecoration(entry);&lt;br /&gt;
        });&lt;br /&gt;
        bindMainPagePlacementRefresh();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPrepared() {&lt;br /&gt;
        var registry = getPreparedRegistrySync() || lastRegistry;&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        if (!registry) return false;&lt;br /&gt;
        renderForCurrentLayout(registry, token);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function syncDecorationState() {&lt;br /&gt;
        if (lastRenderedPageKey === currentPageKey() &amp;amp;&amp;amp; hasRenderedDecorations(document)) {&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (lastRegistry) {&lt;br /&gt;
            renderForCurrentLayout(lastRegistry, runtimeToken);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        return renderPrepared();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleRenderFromCache() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleDecorationVisibilityUpdate() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindNationsPlacementRefresh() {&lt;br /&gt;
        var stack = document.querySelector(&#039;.clbi-nations-panel-stack&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!stack || observedNationsStack === stack) return;&lt;br /&gt;
        observedNationsStack = stack;&lt;br /&gt;
&lt;br /&gt;
        if (nationsPlacementObserver) {&lt;br /&gt;
            nationsPlacementObserver.disconnect();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
            nationsPlacementObserver = new MutationObserver(scheduleDecorationVisibilityUpdate);&lt;br /&gt;
            nationsPlacementObserver.observe(stack, {&lt;br /&gt;
                subtree: true,&lt;br /&gt;
                attributes: true,&lt;br /&gt;
                attributeFilter: [&#039;class&#039;, &#039;hidden&#039;, &#039;aria-selected&#039;, &#039;data-current-era&#039;, &#039;data-nations-current-era&#039;, &#039;data-era-year&#039;]&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedRegistrySync() {&lt;br /&gt;
        var registry = null;&lt;br /&gt;
        var url;&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.getJsonSync === &#039;function&#039;) {&lt;br /&gt;
                registry = window.EntryStore.getJsonSync(REGISTRY_TITLE);&lt;br /&gt;
                if (!registry &amp;amp;&amp;amp; mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                    url = mw.util.getUrl(REGISTRY_TITLE, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
                    registry = window.EntryStore.getJsonSync(url);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return registry &amp;amp;&amp;amp; typeof registry === &#039;object&#039; ? registry : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchRegistry() {&lt;br /&gt;
        var url;&lt;br /&gt;
        var prepared = getPreparedRegistrySync();&lt;br /&gt;
        if (prepared) return Promise.resolve(prepared);&lt;br /&gt;
        if (!mw || !mw.util || typeof fetch !== &#039;function&#039;) return Promise.resolve({ decorations: [] });&lt;br /&gt;
        url = mw.util.getUrl(REGISTRY_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
            .then(function (response) {&lt;br /&gt;
                if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                return response.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (!text || !text.trim()) return { decorations: [] };&lt;br /&gt;
                return JSON.parse(text);&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                return { decorations: [] };&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function reload() {&lt;br /&gt;
        var token;&lt;br /&gt;
        runtimeToken += 1;&lt;br /&gt;
        token = runtimeToken;&lt;br /&gt;
        return fetchRegistry().then(function (registry) {&lt;br /&gt;
            return preloadPixelAssetsForRegistry(registry).then(function () {&lt;br /&gt;
                return renderForCurrentLayout(registry, token);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
        var target = event.target &amp;amp;&amp;amp; event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent], [data-nations-era-move]&#039;) : null;&lt;br /&gt;
        if (target) window.setTimeout(scheduleDecorationVisibilityUpdate, 0);&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    function decorationDiagnostics() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: &#039;20260713-main-body-well-coordinate-003&#039;,&lt;br /&gt;
            hasRegistry: !!lastRegistry,&lt;br /&gt;
            entries: decorationList(lastRegistry).length,&lt;br /&gt;
            rendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            preparedRegistry: !!getPreparedRegistrySync(),&lt;br /&gt;
            pixelAssets: Object.keys(pixelAssetCache || {}).length,&lt;br /&gt;
            pixelCanvases: Object.keys(pixelCanvasCache || {}).length,&lt;br /&gt;
            visibilityOnlySync: true,&lt;br /&gt;
            mainPageCoordinateSurface: MAIN_PAGE_TARGET,&lt;br /&gt;
            mainPagePlacementObserved: !!observedMainPagePortal,&lt;br /&gt;
            lastRenderedPage: lastRenderedPageKey,&lt;br /&gt;
            visibleRendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-visible=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            scheduled: !!scheduledRender&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.Decorations = window.Decorations || {};&lt;br /&gt;
    window.Decorations.reload = reload;&lt;br /&gt;
    window.Decorations.render = render;&lt;br /&gt;
    window.Decorations.renderPrepared = renderPrepared;&lt;br /&gt;
    window.Decorations.sync = syncDecorationState;&lt;br /&gt;
    window.Decorations.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.Decorations.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.Decorations.clear = clearRendered;&lt;br /&gt;
    window.Decorations.apply = applyDecoration;&lt;br /&gt;
    window.Decorations.pageKey = currentPageKey;&lt;br /&gt;
    window.Decorations.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.Decorations.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.Decorations.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.Decorations.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_DECORATIONS = window.CLBI_DECORATIONS || {};&lt;br /&gt;
    window.CLBI_DECORATIONS.reload = reload;&lt;br /&gt;
    window.CLBI_DECORATIONS.render = render;&lt;br /&gt;
    window.CLBI_DECORATIONS.renderPrepared = renderPrepared;&lt;br /&gt;
    window.CLBI_DECORATIONS.sync = syncDecorationState;&lt;br /&gt;
    window.CLBI_DECORATIONS.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.CLBI_DECORATIONS.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.CLBI_DECORATIONS.clear = clearRendered;&lt;br /&gt;
    window.CLBI_DECORATIONS.apply = applyDecoration;&lt;br /&gt;
    window.CLBI_DECORATIONS.pageKey = currentPageKey;&lt;br /&gt;
    window.CLBI_DECORATIONS.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.CLBI_DECORATIONS.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.CLBI_DECORATIONS.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.CLBI_DECORATIONS.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, reload);&lt;br /&gt;
    } else {&lt;br /&gt;
        reload();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            reload();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Unified Shortcuts loader&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!window.mw || !mw.loader) return;&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Nation_List_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:History_Event_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Shortcuts.js&#039;);&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Main page manifesto bitmap renderer&lt;br /&gt;
   Integrated build: no extra raw-script page, no image/data-URL decoding.&lt;br /&gt;
   New shared systems do not use the legacy CLBI prefix.&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Retired compatibility block&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    선언문은 실제 HTML과 ManifestoIntro의 실선 요소만 사용한다.&lt;br /&gt;
    이 구식 렌더러는 제목·본문과 완성형 구분선을 canvas에 한 번에 그려&lt;br /&gt;
    새 애니메이션의 선 뒤에 트랙이 나타날 수 있었다.&lt;br /&gt;
&lt;br /&gt;
    기존 Common.js를 장기 캐시한 뒤 새 파일로 넘어오는 경우를 위해&lt;br /&gt;
    남아 있는 canvas와 상태만 제거하고 즉시 종료한다. 새 공개 이름에는&lt;br /&gt;
    프로젝트 접두사를 사용하지 않는 규칙을 유지한다.&lt;br /&gt;
    */&lt;br /&gt;
    function removeRetiredBitmap() {&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-bitmap&#039;),&lt;br /&gt;
            function (canvas) {&lt;br /&gt;
                if (canvas.parentNode) canvas.parentNode.removeChild(canvas);&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-inner&#039;),&lt;br /&gt;
            function (inner) {&lt;br /&gt;
                inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;data-bitmap-width&#039;,&lt;br /&gt;
                    &#039;data-bitmap-optical-shift-x&#039;,&lt;br /&gt;
                    &#039;data-bitmap-state&#039;,&lt;br /&gt;
                    &#039;data-bitmap-error&#039;&lt;br /&gt;
                ].forEach(function (name) {&lt;br /&gt;
                    inner.removeAttribute(name);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    removeRetiredBitmap();&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(removeRetiredBitmap);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreRetiredBitmapHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:&#039;retired-20260713-divider-no-track-001&#039;,&lt;br /&gt;
        recalculate:removeRetiredBitmap,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            return {&lt;br /&gt;
                integrated:false,&lt;br /&gt;
                state:&#039;retired&#039;,&lt;br /&gt;
                canvas:!!document.querySelector(&#039;.main-portal .main-manifesto-bitmap&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    var VERSION = &#039;20260712-bitmap18-inline-002&#039;;&lt;br /&gt;
    var SELECTOR = &#039;.main-portal .main-manifesto-inner&#039;;&lt;br /&gt;
    var CELL_W = 24;&lt;br /&gt;
    var CELL_H = 24;&lt;br /&gt;
    var GLYPHS = {&amp;quot;진&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,25056,25072,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;창&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,13280,78328,258272,12768,14128,13848,4360,3840,7040,12672,4480,8064,3840,0,0],&amp;quot;바&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12672,12672,14080,13056,13064,258456,127472,12784,12504,12408,12312,12288,12288,4096,0,0],&amp;quot;다&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,12792,12400,12320,258096,128528,14328,13048,12304,12288,12288,12288,4096,0,0],&amp;quot;는&amp;quot;:[19,0,0,0,0,0,0,96,192,192,14528,16064,960,0,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;왕&amp;quot;:[19,0,0,0,0,0,12288,28768,25568,25456,25392,25392,254944,24960,16064,10236,12344,7680,14080,12672,12672,16128,7936,0,0],&amp;quot;과&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,26560,26352,26112,26112,26208,255680,254912,29248,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;노&amp;quot;:[19,0,0,0,0,0,0,0,0,96,192,192,4288,15552,16320,7040,7168,3072,50688,131064,124,0,0,0,0],&amp;quot;예&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,24048,24504,23320,23320,24504,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;를&amp;quot;:[19,0,0,0,0,0,7168,16320,6144,8128,448,15552,1984,122880,262140,16636,8128,8128,3840,8064,1792,960,65408,4096,0],&amp;quot;같&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,110976,127168,12384,12344,4104,3584,0,8064,1920,256,14720,8064,0,0],&amp;quot;은&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,130944,115708,16,448,448,128,128,16256,7936,0,0],&amp;quot;깊&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,25024,24800,24624,8216,12288,16256,4864,15872,7680,31232,32640,256,0],&amp;quot;이&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,25072,25584,25368,25368,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;로&amp;quot;:[19,0,0,0,0,0,0,0,15360,16320,6272,7168,8128,384,14528,16320,7040,3072,50688,131064,124,0,0,0,0],&amp;quot;삼&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,78064,258272,13296,14136,13324,4096,16256,16256,4224,4480,8064,8064,0,0],&amp;quot;켰&amp;quot;:[19,0,0,0,0,0,12288,24576,26112,26600,29560,32656,25072,31992,26720,8224,8216,13056,15104,5056,15232,28544,58992,0,0],&amp;quot;고&amp;quot;:[19,0,0,0,0,0,0,0,0,7680,16352,12416,12288,12288,12544,13056,6912,4864,49920,131064,124,0,0,0,0],&amp;quot;,&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,8,0,0,0],&amp;quot;겨&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,32560,31104,24768,31840,31792,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;울&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,114688,262140,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;사&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12352,12480,12480,12480,12400,258272,127920,14104,13828,13312,12288,12288,12288,4096,0,0],&amp;quot;람&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13296,13240,78208,258552,12304,14224,13296,4096,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;짐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,26592,26160,9756,0,32512,32640,12672,12672,16128,16128,0,0],&amp;quot;승&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,130816,115708,272,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;의&amp;quot;:[19,0,0,0,0,0,28672,57344,24608,25536,26592,26160,26160,26464,25568,24576,24576,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;살&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,110832,127456,13232,13848,5132,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;을&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,32768,130944,115708,0,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;빛&amp;quot;:[19,0,0,0,0,0,12288,24576,25344,25344,26376,25496,25592,25584,25552,25088,7168,12288,32640,6400,7680,32256,25472,0,0],&amp;quot;으&amp;quot;:[19,0,0,0,0,0,0,0,128,3968,8064,4544,4288,6336,7616,3968,0,0,57344,131068,56,0,0,0,0],&amp;quot;얼&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25008,32536,31512,25016,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;렸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,32568,27008,31224,31792,26544,9200,8192,13056,15104,4928,15232,28544,58976,0,0],&amp;quot;.&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0],&amp;quot;그&amp;quot;:[19,0,0,0,0,0,0,0,0,7168,16352,12672,12288,12288,12288,12288,6144,4096,57344,131068,56,0,0,0,0],&amp;quot;들&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130048,118780,120,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;돌&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,8128,130048,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;벽&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25344,32520,31512,25560,32632,25584,9104,8192,31744,32640,8192,8192,8192,8192,0,0],&amp;quot;세&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22720,22720,22752,24432,22752,22960,23320,23300,22528,18432,16384,16384,24576,0,0],&amp;quot;우&amp;quot;:[19,0,0,0,0,0,0,384,7936,6528,12480,6336,7552,3968,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;땅&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,28560,9200,74160,254128,30936,16376,12280,12544,7936,13184,12672,12672,8064,3840,0,0],&amp;quot;찢&amp;quot;:[19,0,0,0,0,0,12288,24576,24704,26616,28656,26560,25456,28656,28056,26848,8192,32512,16128,7680,15872,25344,16768,0,0],&amp;quot;어&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,25520,32536,27416,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;길&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,24768,24672,24624,8216,16256,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;냈&amp;quot;:[19,0,0,0,0,0,8192,58368,19456,27672,27704,31792,31792,32560,28656,27120,8704,13824,14080,13248,15232,28544,50272,0,0],&amp;quot;며&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,26600,32536,25368,25400,32560,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;쇠&amp;quot;:[19,0,0,0,0,0,28672,57344,24704,24960,24960,25056,25536,26208,27888,24960,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;에&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,23024,23480,24344,24344,22968,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;제&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23488,23032,22976,24512,24184,23024,23344,23320,23052,18432,16384,16384,24576,0,0],&amp;quot;름&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,384,130560,116732,56,15872,16320,4288,4544,7552,8064,0,0],&amp;quot;새&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,27840,27840,31968,31856,23776,23984,20248,20228,16384,16384,16384,16384,24576,0,0],&amp;quot;겼&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,30712,32640,25024,31968,26672,8216,8200,15104,15104,6976,15232,28608,58992,0,0],&amp;quot;감&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,4120,4096,16256,16320,4224,4480,8064,8064,0,0],&amp;quot;옥&amp;quot;:[19,0,0,0,0,0,1792,7936,6528,6272,6528,8064,3584,1536,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;되&amp;quot;:[19,0,0,0,0,0,28672,57344,25088,26608,25072,24672,25632,26416,26608,25456,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;군&amp;quot;:[19,0,0,0,0,0,0,7680,16352,12288,12288,12288,6144,126976,262140,18172,1536,1728,1472,192,192,16256,8064,0,0],&amp;quot;대&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28032,28144,31856,31776,23600,24344,20440,19960,16400,16384,16384,16384,24576,0,0],&amp;quot;나&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12296,12312,12336,12304,259600,128784,14256,12528,12320,12288,12288,12288,4096,0,0],&amp;quot;르&amp;quot;:[19,0,0,0,0,0,0,0,6144,16320,6336,7168,8128,448,12480,16320,3968,0,57344,131068,56,0,0,0,0],&amp;quot;오&amp;quot;:[19,0,0,0,0,0,0,0,256,7936,15232,12672,12416,12672,8064,7936,3072,1024,49664,131064,124,0,0,0,0],&amp;quot;래&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28144,27064,32128,32248,23664,23568,20240,20464,16880,16384,16384,16384,24576,0,0],&amp;quot;된&amp;quot;:[19,0,0,0,0,0,28672,57344,26368,25584,24800,25696,28656,26608,29440,32736,25080,24832,25344,768,768,65280,15872,0,0],&amp;quot;원&amp;quot;:[19,0,0,0,0,0,28672,57440,25536,26464,26160,26160,29664,32640,26592,26620,25360,25024,25536,768,768,65280,15872,0,0],&amp;quot;한&amp;quot;:[19,0,0,0,0,0,6144,28704,12512,12800,14328,12784,258528,13088,12592,12784,12512,12416,4544,384,128,16256,7936,0,0],&amp;quot;함&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,14328,78032,258528,13104,12720,12784,4192,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;께&amp;quot;:[19,0,0,0,0,0,24576,55296,55296,55296,55488,57336,57280,57184,57184,55600,55704,55500,55392,49152,49152,16384,16384,0,0],&amp;quot;음&amp;quot;:[19,0,0,0,0,0,1920,8064,6528,6336,6336,3968,1792,32768,131040,16892,0,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;손&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,26304,1584,130560,249852,120,448,448,128,128,16256,7936,0,0],&amp;quot;넘&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,31792,31792,24624,28208,26608,8416,0,32512,32640,12672,12672,16256,15616,0,0],&amp;quot;갔&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,12312,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;\n&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],&amp;quot;줄&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,3840,15296,12480,41056,130976,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;처&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,25072,32448,27616,25392,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;부&amp;quot;:[19,0,0,0,0,0,6144,12288,28768,14400,15552,13248,8128,7872,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;터&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25056,24768,25344,25584,31984,31840,25648,28568,26360,24624,24576,24576,8192,8192,0,0],&amp;quot;목&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;둘&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130944,116732,1592,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;레&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23024,22968,22912,24568,22640,22544,23312,23536,23024,18432,16384,16384,24576,0,0],&amp;quot;있&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,25400,25368,25368,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;었&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32568,32536,25496,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;끝&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,3840,3584,8128,1920,384,16320,4032,0,0],&amp;quot;속&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,7936,12672,26336,1568,130560,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;뿌&amp;quot;:[19,0,0,0,0,0,4096,30720,14080,15904,16352,13280,16320,14208,4608,130944,126972,1552,1536,1536,1536,1536,512,0,0],&amp;quot;리&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,24960,25040,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;와&amp;quot;:[19,0,0,0,0,0,12288,28672,24608,25568,25456,26160,26416,25584,254432,254336,28864,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;빨&amp;quot;:[19,0,0,0,0,0,12288,28672,26112,26176,26304,255944,255992,10200,10224,1728,16256,16256,7936,7936,1792,896,65024,0,0],&amp;quot;굶&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,118784,262140,18428,1536,14080,16368,13792,14272,14272,16352,16352,0,0],&amp;quot;주&amp;quot;:[19,0,0,0,0,0,0,14336,16320,3264,1792,7936,12736,24672,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;림&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,25400,25472,25072,24624,26544,9200,8192,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;매&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28544,28644,32156,32024,23832,23992,19952,19504,16384,16384,16384,16384,24576,0,0],&amp;quot;였&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,32752,32568,25368,32664,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;른&amp;quot;:[19,0,0,0,0,0,4096,16320,6336,7744,4032,14528,16320,896,126976,253948,120,64,448,128,128,16256,7936,0,0],&amp;quot;아&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12304,12528,12720,13080,258840,127896,12784,12352,12288,12288,12288,12288,4096,0,0],&amp;quot;직&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,25568,26160,9752,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;태&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27840,28032,32240,31856,23600,24336,20440,20472,16432,16384,16384,16384,24576,0,0],&amp;quot;지&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25584,24992,24768,24816,25568,26160,27672,25612,24576,24576,24576,8192,0,0],&amp;quot;않&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,7168,6240,32352,15456,13408,14176,8160,0,0],&amp;quot;자&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13304,12720,12480,258296,127984,14128,13848,13324,12288,12288,12288,4096,0,0],&amp;quot;졌&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,32752,31136,32240,32736,26160,9756,8704,13056,15104,4928,15232,28544,50784,0,0],&amp;quot;앞&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,6144,8128,2432,7936,2816,15616,16320,128,0],&amp;quot;갈&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,127360,127168,12384,12344,4104,16256,8064,3840,8064,1792,896,65280,0,0],&amp;quot;때&amp;quot;:[19,0,0,0,0,0,24576,52224,55296,55296,57096,56312,63920,63632,63704,56536,57336,57336,49216,49152,49152,16384,16384,0,0],&amp;quot;마&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13284,13084,13080,258840,127792,12784,12464,12288,12288,12288,12288,4096,0,0],&amp;quot;듭&amp;quot;:[19,0,0,0,0,0,6144,16320,1984,384,12480,16064,3008,32768,131008,115708,14336,12352,12480,14720,16256,8064,7808,0,0],&amp;quot;파&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13248,12792,12688,13296,258528,127392,16352,12412,12288,12288,12288,12288,4096,0,0],&amp;quot;각&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,12672,258240,12384,12336,4120,4096,16128,13248,12288,4096,4096,4096,0,0],&amp;quot;쥔&amp;quot;:[19,0,0,0,0,0,28672,57856,26608,25584,25024,26592,28208,27664,28544,28668,25360,24960,25536,768,768,65280,15872,0,0],&amp;quot;당&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,12536,77920,258096,15888,14328,12920,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;기&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,25392,24960,24768,24672,24624,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;면&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26604,32536,25368,32560,31728,25072,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;풀&amp;quot;:[19,0,0,0,0,0,12288,16352,7296,8064,3328,32704,41440,131008,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;려&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,32696,31104,25040,31856,27696,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;날&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12312,12344,110640,128560,14256,13296,4192,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;것&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,24960,32704,26848,24624,24600,11272,3072,1792,3584,15104,29056,24800,0,0],&amp;quot;라&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13280,12728,12672,12792,258168,127504,13840,14320,12784,12288,12288,12288,4096,0,0],&amp;quot;여&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,32688,25368,25368,32664,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;므&amp;quot;:[19,0,0,0,0,0,0,0,4096,31744,32736,6624,3264,1728,16064,7360,0,0,57344,131068,56,0,0,0,0],&amp;quot;번&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25344,26376,26392,32728,25464,25584,25552,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;도&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,4064,960,384,14528,16064,7136,7360,3072,50688,131064,124,0,0,0,0],&amp;quot;느&amp;quot;:[19,0,0,0,0,0,0,0,0,32,192,192,192,14528,16064,7104,0,0,57344,131068,56,0,0,0,0],&amp;quot;슨&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,24768,48,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;해&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27968,28664,32240,32224,23984,23984,19952,19568,16384,16384,16384,16384,24576,0,0],&amp;quot;았&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12528,12784,78616,258840,13208,12784,12512,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;침&amp;quot;:[19,0,0,0,0,0,12288,24768,25024,25088,26592,25584,24800,25568,26416,9232,8,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;내&amp;quot;:[19,0,0,0,0,0,24576,58368,19456,19456,27656,27672,31792,31792,31760,24368,20464,19952,16384,16384,24576,24576,8192,0,0],&amp;quot;멎&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25576,32536,31512,25392,25072,8224,8192,32640,6912,3584,15872,25344,16768,0,0],&amp;quot;서&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,32496,24800,25520,26392,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;긴&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26368,25592,25520,25024,24768,24688,24632,24584,25472,8960,768,768,32512,15872,0,0],&amp;quot;붙&amp;quot;:[19,0,0,0,0,0,6144,12288,14432,16064,13248,7872,38912,130816,116732,1552,3840,3584,8064,1920,384,15808,4032,0,0],&amp;quot;잡&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,111040,258288,13280,13872,5660,4096,12352,12416,14720,16256,8064,7296,0,0],&amp;quot;피&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25592,25392,25568,25440,24992,28640,26744,24576,24576,24576,24576,8192,0,0],&amp;quot;가&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,13304,12728,12736,127168,258144,12336,12312,12300,12288,12288,12288,4096,0,0],&amp;quot;굴&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,126976,262140,18172,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;보&amp;quot;:[19,0,0,0,0,0,0,0,6144,12288,28768,14528,16064,13248,16320,7360,3072,1024,50688,131064,124,0,0,0,0],&amp;quot;하&amp;quot;:[19,0,0,0,0,0,6144,12288,12320,12512,12800,14332,12792,12512,259040,127792,12720,12528,12288,12288,12288,12288,4096,0,0],&amp;quot;입&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25584,25368,25368,25496,25072,8256,4096,28800,24960,14720,16128,16128,15616,0,0],&amp;quot;말&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13308,111384,127768,13240,12784,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;했&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,28664,27888,32224,23984,19888,18672,96,13824,15872,13184,15232,28544,50272,0,0],&amp;quot;조&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,8128,3072,1792,16128,28864,25184,1568,1536,50688,131064,124,0,0,0,0],&amp;quot;올&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6528,8064,3840,1536,128768,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;수&amp;quot;:[19,0,0,0,0,0,0,1536,3072,1792,1792,6912,29056,24800,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;록&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,3968,128512,253948,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;열&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,32688,31512,25368,32696,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;단&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,12792,12400,258096,13872,14232,14328,12336,12672,4480,384,384,16256,7936,0,0],&amp;quot;질&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25584,24992,25072,26464,26160,9240,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;히&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25024,26112,26616,25584,25024,25440,25392,25392,25056,24576,24576,24576,8192,8192,0,0],&amp;quot;운&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;데&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22912,23024,22640,24352,22576,23320,23512,23032,22544,18432,16384,16384,24576,0,0],&amp;quot;끌&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;린&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25568,25464,24960,25592,24624,26416,26608,24672,8320,9088,256,256,32512,15872,0,0],&amp;quot;게&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,22960,24000,24512,22624,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;누&amp;quot;:[19,0,0,0,0,0,0,96,192,192,192,15552,16320,960,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;인&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24624,25072,25400,25368,25368,25072,24800,24576,9088,8960,256,256,32512,15872,0,0],&amp;quot;쳐&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,32752,31936,25568,32560,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;문&amp;quot;:[19,0,0,0,0,0,12288,15872,15328,6336,3264,7872,7360,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;몸&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;식&amp;quot;:[19,0,0,0,0,0,12288,24576,24768,24960,25024,24816,24800,25568,26160,9244,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;억&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32536,32536,25368,25072,8416,0,31744,32640,12288,12288,12288,12288,0,0],&amp;quot;전&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25592,24992,31984,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;품&amp;quot;:[19,0,0,0,0,0,12288,16352,7360,8064,7040,16128,26592,127040,253948,18040,1536,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;발&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,111512,127992,13176,13272,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;머&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,25576,25368,32536,27448,25392,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;두&amp;quot;:[19,0,0,0,0,0,0,14336,8128,960,384,12480,16064,5056,192,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;닐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,24624,24624,27696,28464,26608,8288,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;격&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26616,32640,25024,31968,26672,8216,8200,32256,26496,8192,8192,8192,8192,0,0],&amp;quot;없&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25072,32536,31512,25496,25072,8256,8192,13824,15936,4928,15296,28608,51008,0,0],&amp;quot;니&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24584,24624,24624,24624,25648,28208,26544,25328,24576,24576,24576,8192,8192,0,0],&amp;quot;산&amp;quot;:[19,0,0,0,0,0,6144,28672,12352,12480,12480,12496,258272,13280,14128,13852,12288,12672,4480,384,384,16256,7936,0,0],&amp;quot;복&amp;quot;:[19,0,0,0,0,0,14336,12288,28736,15552,14272,16320,7808,3072,130560,249852,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;종&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,26208,1568,130816,116732,280,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;죽&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,24672,122912,262140,18172,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;망&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,78620,258840,13112,12784,12336,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;베&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22912,22912,24448,24448,24456,24536,23024,23024,22776,22648,18456,16384,16384,24576,0,0],&amp;quot;막&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,13084,258840,13080,12784,12464,4096,15872,16320,12288,4096,4096,4096,0,0],&amp;quot;무&amp;quot;:[19,0,0,0,0,0,0,30720,32608,15296,6336,3520,16320,15488,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;릎&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,8128,0,130560,116732,56,16320,3456,8064,3840,16128,14272,0,0],&amp;quot;꿇&amp;quot;:[19,0,0,0,0,0,0,14720,16368,12640,12672,12672,118912,262140,18428,1536,7936,7152,32736,14784,11712,16352,7648,0,0],&amp;quot;까&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,24576,24768,10232,10176,9824,254752,254768,8600,8388,8288,8192,8192,8192,12288,0,0],&amp;quot;놓&amp;quot;:[19,0,0,0,0,0,0,224,192,12480,15552,8128,7552,130048,253948,16760,1792,15872,16320,3584,6912,3328,1792,0,0],&amp;quot;러&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,29056,32720,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;재&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28608,28152,32192,31936,23672,24048,20272,20248,16908,16384,16384,16384,24576,0,0],&amp;quot;받&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,78744,259064,13176,13304,12672,0,7936,3584,768,24960,32640,1920,0,0],&amp;quot;묵&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,3200,130048,118780,1592,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;스&amp;quot;:[19,0,0,0,0,0,0,0,512,1536,3584,1920,1792,7936,12672,24800,0,0,57344,131068,56,0,0,0,0],&amp;quot;릴&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25592,25360,25536,25072,25136,26608,8432,14464,16256,7936,16128,3840,1792,130560,0,0],&amp;quot;계&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,24496,24256,22720,24416,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;구&amp;quot;:[19,0,0,0,0,0,0,6144,16352,12736,12288,12288,12288,6144,4096,131056,67324,1536,1536,1536,1536,1536,512,0,0],&amp;quot;물&amp;quot;:[19,0,0,0,0,0,14336,32544,15328,3264,7872,16064,32768,131040,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;못&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,7360,3776,16064,7296,3072,130816,116732,1048,3584,1536,1792,7936,12480,8304,0,0],&amp;quot;시&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,24816,24800,25520,26424,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;육&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6336,6528,3968,1536,130048,118780,6584,6528,16256,15296,4096,4096,4096,4096,0,0],&amp;quot;신&amp;quot;:[19,0,0,0,0,0,12288,24576,24640,25024,25024,24768,24800,25568,26416,25628,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;일&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25584,25368,25368,25528,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;모&amp;quot;:[19,0,0,0,0,0,0,0,14336,32256,16352,6368,3264,7872,16064,6144,2048,1024,50688,131064,124,0,0,0,0],&amp;quot;행&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,20472,27888,32224,32176,23984,19696,25184,15872,30208,25344,25344,16128,7680,0,0],&amp;quot;절&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,32160,32752,26464,26160,9752,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;멸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,32760,31512,25400,32688,25072,8224,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;끊&amp;quot;:[19,0,0,0,0,0,0,14592,16368,13280,12672,12672,12672,114688,262140,16636,7168,6240,32352,15456,13408,16224,7648,0,0],&amp;quot;순&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,131008,116732,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;간&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,13304,12688,258240,12384,12336,12312,12296,12672,4480,384,384,16256,7936,0,0]};&lt;br /&gt;
    var BODY_SCALE = 1;&lt;br /&gt;
    var TITLE_SCALE = 2;&lt;br /&gt;
    var BODY_TRACKING = -2;&lt;br /&gt;
    var TITLE_TRACKING = 0;&lt;br /&gt;
    var SPACE_ADVANCE = Math.round(CELL_H * 0.5);&lt;br /&gt;
    var BODY_LINE_HEIGHT = CELL_H + 8;&lt;br /&gt;
    var PARAGRAPH_GAP = 20;&lt;br /&gt;
    var DIVIDER_Y = 52;&lt;br /&gt;
    var BODY_TOP = 68;&lt;br /&gt;
    var BODY_COLOR = &#039;#d8d8d8&#039;;&lt;br /&gt;
    var TITLE_COLOR = &#039;#ffffff&#039;;&lt;br /&gt;
    var DIVIDER_COLOR = &#039;#555555&#039;;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var observed = typeof WeakSet === &#039;function&#039; ? new WeakSet() : null;&lt;br /&gt;
    var resizeObserver = typeof ResizeObserver === &#039;function&#039; ? new ResizeObserver(function (entries) {&lt;br /&gt;
        entries.forEach(function (entry) { schedule(entry.target, false); });&lt;br /&gt;
    }) : null;&lt;br /&gt;
&lt;br /&gt;
    function glyphFor(character) {&lt;br /&gt;
        return Object.prototype.hasOwnProperty.call(GLYPHS, character) ? GLYPHS[character] : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function measureText(text, scale, tracking) {&lt;br /&gt;
        var width = 0;&lt;br /&gt;
        var glyphCount = 0;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                width += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            width += glyph[0] * scale;&lt;br /&gt;
            width += tracking * scale;&lt;br /&gt;
            glyphCount += 1;&lt;br /&gt;
        });&lt;br /&gt;
        if (glyphCount &amp;amp;&amp;amp; tracking) width -= tracking * scale;&lt;br /&gt;
        return Math.max(0, Math.round(width));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function breakLongToken(token, maxWidth) {&lt;br /&gt;
        var pieces = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
        Array.from(token).forEach(function (character) {&lt;br /&gt;
            var candidate = current + character;&lt;br /&gt;
            if (current &amp;amp;&amp;amp; measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;gt; maxWidth) {&lt;br /&gt;
                pieces.push(current);&lt;br /&gt;
                current = character;&lt;br /&gt;
            } else {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        if (current) pieces.push(current);&lt;br /&gt;
        return pieces;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wrapParagraph(text, maxWidth) {&lt;br /&gt;
        var words = String(text || &#039;&#039;).trim().split(/\s+/).filter(Boolean);&lt;br /&gt;
        var lines = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        words.forEach(function (word) {&lt;br /&gt;
            var candidate = current ? current + &#039; &#039; + word : word;&lt;br /&gt;
            var pieces;&lt;br /&gt;
            if (measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (current) {&lt;br /&gt;
                lines.push(current);&lt;br /&gt;
                current = &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (measureText(word, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = word;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            pieces = breakLongToken(word, maxWidth);&lt;br /&gt;
            pieces.forEach(function (piece, index) {&lt;br /&gt;
                if (index === pieces.length - 1) current = piece;&lt;br /&gt;
                else lines.push(piece);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        if (current) lines.push(current);&lt;br /&gt;
        return lines.length ? lines : [&#039;&#039;];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateOpticalShift(lines, width) {&lt;br /&gt;
        var weightedCenter = 0;&lt;br /&gt;
        var totalWeight = 0;&lt;br /&gt;
        var maxLineWidth = 0;&lt;br /&gt;
        var shift;&lt;br /&gt;
        lines.forEach(function (line) {&lt;br /&gt;
            var lineWidth = line.width;&lt;br /&gt;
            var weight = Math.max(1, lineWidth) * BODY_LINE_HEIGHT;&lt;br /&gt;
            weightedCenter += (lineWidth / 2) * weight;&lt;br /&gt;
            totalWeight += weight;&lt;br /&gt;
            maxLineWidth = Math.max(maxLineWidth, lineWidth);&lt;br /&gt;
        });&lt;br /&gt;
        if (!totalWeight) return 0;&lt;br /&gt;
        shift = Math.round((width / 2) - (weightedCenter / totalWeight));&lt;br /&gt;
        return Math.max(0, Math.min(Math.max(0, width - maxLineWidth), shift));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawGlyph(context, glyph, x, y, scale) {&lt;br /&gt;
        var py;&lt;br /&gt;
        var px;&lt;br /&gt;
        var row;&lt;br /&gt;
        for (py = 0; py &amp;lt; CELL_H; py += 1) {&lt;br /&gt;
            row = glyph[py + 1] || 0;&lt;br /&gt;
            if (!row) continue;&lt;br /&gt;
            for (px = 0; px &amp;lt; CELL_W; px += 1) {&lt;br /&gt;
                if (row &amp;amp; (1 &amp;lt;&amp;lt; px)) {&lt;br /&gt;
                    context.fillRect(&lt;br /&gt;
                        Math.round(x + px * scale),&lt;br /&gt;
                        Math.round(y + py * scale),&lt;br /&gt;
                        scale,&lt;br /&gt;
                        scale&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawText(context, text, x, y, scale, tracking, color) {&lt;br /&gt;
        context.fillStyle = color;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                x += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            drawGlyph(context, glyph, x, y, scale);&lt;br /&gt;
            x += (glyph[0] + tracking) * scale;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sourceContent(inner) {&lt;br /&gt;
        var titleNode = inner.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        var paragraphs = [];&lt;br /&gt;
        Array.prototype.forEach.call(inner.children || [], function (child) {&lt;br /&gt;
            if (child &amp;amp;&amp;amp; child.tagName === &#039;P&#039;) {&lt;br /&gt;
                var value = String(child.textContent || &#039;&#039;).trim();&lt;br /&gt;
                if (value) paragraphs.push(value);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        return {&lt;br /&gt;
            title: titleNode ? String(titleNode.textContent || &#039;&#039;).trim() : &#039;&#039;,&lt;br /&gt;
            paragraphs: paragraphs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureCanvas(inner) {&lt;br /&gt;
        var canvas = inner.querySelector(&#039;.main-manifesto-bitmap&#039;);&lt;br /&gt;
        if (!canvas) {&lt;br /&gt;
            canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            canvas.className = &#039;main-manifesto-bitmap&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            canvas.setAttribute(&#039;data-bitmap-version&#039;, VERSION);&lt;br /&gt;
            inner.insertBefore(canvas, inner.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
        return canvas;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(inner, force) {&lt;br /&gt;
        var width;&lt;br /&gt;
        var source;&lt;br /&gt;
        var paragraphs;&lt;br /&gt;
        var flatLines = [];&lt;br /&gt;
        var titleWidth;&lt;br /&gt;
        var height;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var context;&lt;br /&gt;
        var y;&lt;br /&gt;
        var opticalShift;&lt;br /&gt;
&lt;br /&gt;
        if (!inner || !inner.isConnected) return;&lt;br /&gt;
        width = Math.max(1, Math.floor(inner.clientWidth || inner.getBoundingClientRect().width || 0));&lt;br /&gt;
        if (width &amp;lt;= 1) {&lt;br /&gt;
            window.setTimeout(function () { schedule(inner, true); }, 60);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (!force &amp;amp;&amp;amp; Number(inner.getAttribute(&#039;data-bitmap-width&#039;)) === width &amp;amp;&amp;amp; inner.classList.contains(&#039;is-bitmap-ready&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        source = sourceContent(inner);&lt;br /&gt;
        if (!source.title || !source.paragraphs.length) {&lt;br /&gt;
            fail(inner, new Error(&#039;Manifesto source nodes were not found.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        paragraphs = source.paragraphs.map(function (paragraph) {&lt;br /&gt;
            return wrapParagraph(paragraph, width).map(function (line) {&lt;br /&gt;
                var item = { text:line, width:measureText(line, BODY_SCALE, BODY_TRACKING) };&lt;br /&gt;
                flatLines.push(item);&lt;br /&gt;
                return item;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        titleWidth = measureText(source.title, TITLE_SCALE, TITLE_TRACKING);&lt;br /&gt;
        height = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, index) {&lt;br /&gt;
            height += lines.length * BODY_LINE_HEIGHT;&lt;br /&gt;
            if (index &amp;lt; paragraphs.length - 1) height += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
        height = Math.max(height, DIVIDER_Y + 2);&lt;br /&gt;
&lt;br /&gt;
        canvas = ensureCanvas(inner);&lt;br /&gt;
        canvas.width = width;&lt;br /&gt;
        canvas.height = Math.ceil(height);&lt;br /&gt;
        canvas.style.width = width + &#039;px&#039;;&lt;br /&gt;
        canvas.style.height = Math.ceil(height) + &#039;px&#039;;&lt;br /&gt;
        context = canvas.getContext(&#039;2d&#039;, { alpha:true });&lt;br /&gt;
        if (!context) {&lt;br /&gt;
            fail(inner, new Error(&#039;Canvas 2D context is unavailable.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        context.imageSmoothingEnabled = false;&lt;br /&gt;
        context.clearRect(0, 0, canvas.width, canvas.height);&lt;br /&gt;
&lt;br /&gt;
        drawText(&lt;br /&gt;
            context,&lt;br /&gt;
            source.title,&lt;br /&gt;
            Math.round((width - titleWidth) / 2),&lt;br /&gt;
            0,&lt;br /&gt;
            TITLE_SCALE,&lt;br /&gt;
            TITLE_TRACKING,&lt;br /&gt;
            TITLE_COLOR&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /* 완성형 구분선은 폐기했다. 실선 애니메이션은 ManifestoIntro만 담당한다. */&lt;br /&gt;
&lt;br /&gt;
        opticalShift = calculateOpticalShift(flatLines, width);&lt;br /&gt;
        y = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, paragraphIndex) {&lt;br /&gt;
            lines.forEach(function (line) {&lt;br /&gt;
                drawText(context, line.text, opticalShift, y, BODY_SCALE, BODY_TRACKING, BODY_COLOR);&lt;br /&gt;
                y += BODY_LINE_HEIGHT;&lt;br /&gt;
            });&lt;br /&gt;
            if (paragraphIndex &amp;lt; paragraphs.length - 1) y += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-width&#039;, String(width));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-optical-shift-x&#039;, String(opticalShift));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        inner.classList.add(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fail(inner, error) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;failed&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-error&#039;, String(error &amp;amp;&amp;amp; error.message ? error.message : error));&lt;br /&gt;
        try { console.error(&#039;[MainPageBitmap]&#039;, error); } catch (ignore) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(inner, force) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            try { render(inner, force); }&lt;br /&gt;
            catch (error) { fail(inner, error); }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mount(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var nodes = [];&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(SELECTOR)) nodes.push(scope);&lt;br /&gt;
        nodes = nodes.concat(Array.prototype.slice.call(scope.querySelectorAll(SELECTOR)));&lt;br /&gt;
        nodes.forEach(function (inner) {&lt;br /&gt;
            if (resizeObserver &amp;amp;&amp;amp; (!observed || !observed.has(inner))) {&lt;br /&gt;
                resizeObserver.observe(inner);&lt;br /&gt;
                if (observed) observed.add(inner);&lt;br /&gt;
            }&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function recalculate() {&lt;br /&gt;
        Array.prototype.forEach.call(document.querySelectorAll(SELECTOR), function (inner) {&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot() {&lt;br /&gt;
        mount(document);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 120);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) document.addEventListener(&#039;DOMContentLoaded&#039;, boot, { once:true });&lt;br /&gt;
    else boot();&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        window.clearTimeout(resizeTimer);&lt;br /&gt;
        resizeTimer = window.setTimeout(recalculate, 100);&lt;br /&gt;
    }, { passive:true });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                mount(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        recalculate:recalculate,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            var inner = document.querySelector(SELECTOR);&lt;br /&gt;
            return inner ? {&lt;br /&gt;
                integrated:true,&lt;br /&gt;
                state:inner.getAttribute(&#039;data-bitmap-state&#039;) || &#039;pending&#039;,&lt;br /&gt;
                error:inner.getAttribute(&#039;data-bitmap-error&#039;) || &#039;&#039;,&lt;br /&gt;
                width:inner.getAttribute(&#039;data-bitmap-width&#039;) || &#039;&#039;,&lt;br /&gt;
                opticalShiftX:inner.getAttribute(&#039;data-bitmap-optical-shift-x&#039;) || &#039;&#039;,&lt;br /&gt;
                canvas:!!inner.querySelector(&#039;.main-manifesto-bitmap&#039;)&lt;br /&gt;
            } : { integrated:true, state:&#039;not-mounted&#039; };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    window.MainPageManifesto = window.MainPageManifesto || {};&lt;br /&gt;
    window.MainPageManifesto.recalculate = recalculate;&lt;br /&gt;
})(window, document, window.mw);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4658</id>
		<title>미디어위키:Common.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4658"/>
		<updated>2026-07-17T00:52:08Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-bottom-visible-gap-2px-20260717 / Common.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
   COASTLINE: BLACK ICE - Common (Entry Point)&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css는 CSS 로더 역할만 한다.&lt;br /&gt;
&lt;br /&gt;
정리 원칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 공통 토큰과 전역 테마: Theme.css&lt;br /&gt;
- 틀과 문서 컴포넌트: Template.css, Components.css&lt;br /&gt;
- 아이콘: Icons.css&lt;br /&gt;
- 레이아웃: Layout.css&lt;br /&gt;
- 편집기: Editor.css&lt;br /&gt;
- 다이얼로그: Dialogs.css&lt;br /&gt;
- 좌측 언어 다이얼: Layout.css + Common.js&lt;br /&gt;
- 대문 전용: MainPage.css&lt;br /&gt;
- 테스트 전용: Test.css&lt;br /&gt;
&lt;br /&gt;
주의:&lt;br /&gt;
Test.css는 실험용이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
@import url(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Template.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-era-page-hotfix-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Icons.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Layout.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Theme.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260712-yisunshin-galmuri-method-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Editor.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Components.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Dialogs.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:MainPage.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260717-portal-section-bottom-gap-two-045&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:DevTools.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Nation_List_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix57&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:History_Event_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:PortalSectionNav.js&amp;diff=4657</id>
		<title>미디어위키:PortalSectionNav.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:PortalSectionNav.js&amp;diff=4657"/>
		<updated>2026-07-17T00:46:16Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-separated-outline-effects-20260717 / PortalSectionNav.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
PortalSectionNav&lt;br /&gt;
대문 상단 카테고리 하단 부착형 섹션 버튼&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    var XLINK_NS = &#039;http://www.w3.org/1999/xlink&#039;;&lt;br /&gt;
    var COMPONENT = &#039;portal-section-nav&#039;;&lt;br /&gt;
    var VERSION = &#039;20260717-portal-section-layered-outline-044&#039;;&lt;br /&gt;
    var BUTTON_HEIGHT = 22;&lt;br /&gt;
    var TOP_FRAME = 8;&lt;br /&gt;
    var BOTTOM_FRAME = 8;&lt;br /&gt;
    var GAP = 2;&lt;br /&gt;
    var ROW_HEIGHT = TOP_FRAME + GAP + BUTTON_HEIGHT + GAP + BOTTOM_FRAME;&lt;br /&gt;
    var BUTTON_TOP = TOP_FRAME + GAP;&lt;br /&gt;
    var BUTTON_BOTTOM = BUTTON_TOP + BUTTON_HEIGHT;&lt;br /&gt;
    var OUTER_BORDER = 1;&lt;br /&gt;
    var INSET_OFFSET = 1.5;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var renderTimer = 0;&lt;br /&gt;
    var observer = null;&lt;br /&gt;
    var ITEMS = [&lt;br /&gt;
        { key:&#039;black-ice&#039;, label:&#039;블랙 아이스&#039;, title:&#039;대문&#039; },&lt;br /&gt;
        {&lt;br /&gt;
            key:&#039;anecdote&#039;,&lt;br /&gt;
            label:&#039;에넥도트&#039;,&lt;br /&gt;
            href:&#039;/index.php/특수:모든문서?namespace=3000&#039;&lt;br /&gt;
        }&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    function createSvgElement(name) {&lt;br /&gt;
        return document.createElementNS(SVG_NS, name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildWikiPath(title) {&lt;br /&gt;
        if (typeof window.buildWikiPath === &#039;function&#039;) {&lt;br /&gt;
            return window.buildWikiPath(title);&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function roundCoordinate(value) {&lt;br /&gt;
        return Math.round(value * 10000) / 10000;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function pointsToString(points) {&lt;br /&gt;
        return points.map(function (point) {&lt;br /&gt;
            return roundCoordinate(point[0]) + &#039;,&#039; + roundCoordinate(point[1]);&lt;br /&gt;
        }).join(&#039; &#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function parsePoints(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).trim().split(/\s+/).map(function (pair) {&lt;br /&gt;
            var parts = pair.split(&#039;,&#039;);&lt;br /&gt;
            return [Number(parts[0]), Number(parts[1])];&lt;br /&gt;
        }).filter(function (point) {&lt;br /&gt;
            return Number.isFinite(point[0]) &amp;amp;&amp;amp; Number.isFinite(point[1]);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getCentroid(points) {&lt;br /&gt;
        var x = 0;&lt;br /&gt;
        var y = 0;&lt;br /&gt;
&lt;br /&gt;
        points.forEach(function (point) {&lt;br /&gt;
            x += point[0];&lt;br /&gt;
            y += point[1];&lt;br /&gt;
        });&lt;br /&gt;
        return [x / points.length, y / points.length];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function lineXAtY(pointA, pointB, y) {&lt;br /&gt;
        var dy = pointB[1] - pointA[1];&lt;br /&gt;
        if (Math.abs(dy) &amp;lt; 0.000001) return pointA[0];&lt;br /&gt;
        return pointA[0] + (y - pointA[1]) * (pointB[0] - pointA[0]) / dy;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function projectSidesToY(points, top, bottom, horizontalInset) {&lt;br /&gt;
        var inset = Number(horizontalInset) || 0;&lt;br /&gt;
        return [&lt;br /&gt;
            [lineXAtY(points[0], points[3], top) + inset, top],&lt;br /&gt;
            [lineXAtY(points[1], points[2], top) - inset, top],&lt;br /&gt;
            [lineXAtY(points[1], points[2], bottom) - inset, bottom],&lt;br /&gt;
            [lineXAtY(points[0], points[3], bottom) + inset, bottom]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPolygon(className, points) {&lt;br /&gt;
        var polygon = createSvgElement(&#039;polygon&#039;);&lt;br /&gt;
        polygon.setAttribute(&#039;class&#039;, className);&lt;br /&gt;
        polygon.setAttribute(&#039;points&#039;, pointsToString(points));&lt;br /&gt;
        polygon.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        return polygon;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createLine(className, start, end) {&lt;br /&gt;
        var line = createSvgElement(&#039;line&#039;);&lt;br /&gt;
        line.setAttribute(&#039;class&#039;, className);&lt;br /&gt;
        line.setAttribute(&#039;x1&#039;, roundCoordinate(start[0]));&lt;br /&gt;
        line.setAttribute(&#039;y1&#039;, roundCoordinate(start[1]));&lt;br /&gt;
        line.setAttribute(&#039;x2&#039;, roundCoordinate(end[0]));&lt;br /&gt;
        line.setAttribute(&#039;y2&#039;, roundCoordinate(end[1]));&lt;br /&gt;
        line.setAttribute(&#039;vector-effect&#039;, &#039;non-scaling-stroke&#039;);&lt;br /&gt;
        line.setAttribute(&#039;pointer-events&#039;, &#039;none&#039;);&lt;br /&gt;
        return line;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getViewBox(svg) {&lt;br /&gt;
        var base = svg &amp;amp;&amp;amp; svg.viewBox &amp;amp;&amp;amp; svg.viewBox.baseVal;&lt;br /&gt;
        var values;&lt;br /&gt;
&lt;br /&gt;
        if (base &amp;amp;&amp;amp; base.width &amp;gt; 0 &amp;amp;&amp;amp; base.height &amp;gt; 0) {&lt;br /&gt;
            return { x:base.x, y:base.y, width:base.width, height:base.height };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        values = String(svg.getAttribute(&#039;viewBox&#039;) || &#039;&#039;).trim().split(/[\s,]+/).map(Number);&lt;br /&gt;
        if (values.length === 4 &amp;amp;&amp;amp; values[2] &amp;gt; 0 &amp;amp;&amp;amp; values[3] &amp;gt; 0) {&lt;br /&gt;
            return { x:values[0], y:values[1], width:values[2], height:values[3] };&lt;br /&gt;
        }&lt;br /&gt;
        return null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureMount(portal, topMount) {&lt;br /&gt;
        var mount = portal.querySelector(&#039;[data-component=&amp;quot;&#039; + COMPONENT + &#039;&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!mount) {&lt;br /&gt;
            mount = document.createElement(&#039;div&#039;);&lt;br /&gt;
            mount.setAttribute(&#039;data-component&#039;, COMPONENT);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (mount.parentNode !== topMount.parentNode || topMount.nextSibling !== mount) {&lt;br /&gt;
            topMount.parentNode.insertBefore(mount, topMount.nextSibling);&lt;br /&gt;
        }&lt;br /&gt;
        return mount;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fallbackGeometry(width) {&lt;br /&gt;
        var cut = Math.max(6, Math.min(10, Math.round((width / 5) * 0.045)));&lt;br /&gt;
        var run = Math.round(cut * ROW_HEIGHT / 22);&lt;br /&gt;
        var outer = [&lt;br /&gt;
            [Math.round(width * 0.2), 0],&lt;br /&gt;
            [Math.round(width * 0.8), 0],&lt;br /&gt;
            [Math.round(width * 0.8) - run, ROW_HEIGHT],&lt;br /&gt;
            [Math.round(width * 0.2) + run, ROW_HEIGHT]&lt;br /&gt;
        ];&lt;br /&gt;
        return {&lt;br /&gt;
            outer:outer,&lt;br /&gt;
            leftDirection:projectSidesToY(outer, 0, ROW_HEIGHT - 1, INSET_OFFSET).map(function (point, index) {&lt;br /&gt;
                return index === 0 || index === 3 ? point : null;&lt;br /&gt;
            }).filter(Boolean),&lt;br /&gt;
            rightDirection:projectSidesToY(outer, 0, ROW_HEIGHT - 1, INSET_OFFSET).slice(1, 3)&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readFrameGeometry(topMount, mount, width) {&lt;br /&gt;
        var topSvg = topMount.querySelector(&#039;.portal-category-svg&#039;);&lt;br /&gt;
        var settingsAnchor = topMount.querySelector(&#039;.portal-cat-anchor[data-category-key=&amp;quot;settings&amp;quot;]&#039;);&lt;br /&gt;
        var corporationAnchor = topMount.querySelector(&#039;.portal-cat-anchor[data-category-key=&amp;quot;corporations&amp;quot;]&#039;);&lt;br /&gt;
        var settings = settingsAnchor &amp;amp;&amp;amp; settingsAnchor.querySelector(&#039;.portal-cat-edge&#039;);&lt;br /&gt;
        var corporations = corporationAnchor &amp;amp;&amp;amp; corporationAnchor.querySelector(&#039;.portal-cat-edge&#039;);&lt;br /&gt;
        var settingsInset = settingsAnchor &amp;amp;&amp;amp; settingsAnchor.querySelector(&#039;.portal-cat-inset-left&#039;);&lt;br /&gt;
        var corporationInset = corporationAnchor &amp;amp;&amp;amp; corporationAnchor.querySelector(&#039;.portal-cat-inset-right&#039;);&lt;br /&gt;
        var settingsPoints = settings ? parsePoints(settings.getAttribute(&#039;points&#039;)) : [];&lt;br /&gt;
        var corporationPoints = corporations ? parsePoints(corporations.getAttribute(&#039;points&#039;)) : [];&lt;br /&gt;
        var settingsInsetPoints = settingsInset ? [&lt;br /&gt;
            [Number(settingsInset.getAttribute(&#039;x1&#039;)), Number(settingsInset.getAttribute(&#039;y1&#039;))],&lt;br /&gt;
            [Number(settingsInset.getAttribute(&#039;x2&#039;)), Number(settingsInset.getAttribute(&#039;y2&#039;))]&lt;br /&gt;
        ] : [];&lt;br /&gt;
        var corporationInsetPoints = corporationInset ? [&lt;br /&gt;
            [Number(corporationInset.getAttribute(&#039;x1&#039;)), Number(corporationInset.getAttribute(&#039;y1&#039;))],&lt;br /&gt;
            [Number(corporationInset.getAttribute(&#039;x2&#039;)), Number(corporationInset.getAttribute(&#039;y2&#039;))]&lt;br /&gt;
        ] : [];&lt;br /&gt;
        var svgRect;&lt;br /&gt;
        var mountRect;&lt;br /&gt;
        var viewBox;&lt;br /&gt;
        var scaleX;&lt;br /&gt;
        var scaleY;&lt;br /&gt;
        var outer;&lt;br /&gt;
        var fallback;&lt;br /&gt;
&lt;br /&gt;
        if (!topSvg || settingsPoints.length !== 4 || corporationPoints.length !== 4) {&lt;br /&gt;
            return fallbackGeometry(width);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        svgRect = topSvg.getBoundingClientRect();&lt;br /&gt;
        mountRect = mount.getBoundingClientRect();&lt;br /&gt;
        viewBox = getViewBox(topSvg);&lt;br /&gt;
        if (&lt;br /&gt;
            !viewBox ||&lt;br /&gt;
            !(svgRect.width &amp;gt; 0) ||&lt;br /&gt;
            !(svgRect.height &amp;gt; 0) ||&lt;br /&gt;
            !(mountRect.width &amp;gt; 0)&lt;br /&gt;
        ) {&lt;br /&gt;
            return fallbackGeometry(width);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        scaleX = svgRect.width / viewBox.width;&lt;br /&gt;
        scaleY = svgRect.height / viewBox.height;&lt;br /&gt;
&lt;br /&gt;
        function screenPoint(point) {&lt;br /&gt;
            return [&lt;br /&gt;
                svgRect.left + (point[0] - viewBox.x) * scaleX,&lt;br /&gt;
                svgRect.top + (point[1] - viewBox.y) * scaleY&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function localLineXAt(pointA, pointB, localY) {&lt;br /&gt;
            var a = screenPoint(pointA);&lt;br /&gt;
            var b = screenPoint(pointB);&lt;br /&gt;
            var targetY = mountRect.top + localY;&lt;br /&gt;
            var dy = b[1] - a[1];&lt;br /&gt;
            var screenX;&lt;br /&gt;
&lt;br /&gt;
            if (Math.abs(dy) &amp;lt; 0.000001) {&lt;br /&gt;
                return a[0] - mountRect.left;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            screenX = a[0] + (targetY - a[1]) * (b[0] - a[0]) / dy;&lt;br /&gt;
            return screenX - mountRect.left;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 설정 버튼의 왼쪽 사선과 기업 버튼의 오른쪽 사선을 화면 좌표계의&lt;br /&gt;
         * 직선으로 읽고, 새 프레임의 상단·하단 높이까지 그대로 연장한다.&lt;br /&gt;
         * 단순히 기존 x 편차를 재사용하지 않으므로 행 높이가 달라도 경사각이&lt;br /&gt;
         * 바뀌지 않으며 두 프레임 사이의 9px 겹침 안에서도 한 선으로 이어진다.&lt;br /&gt;
         */&lt;br /&gt;
        outer = [&lt;br /&gt;
            [localLineXAt(settingsPoints[0], settingsPoints[3], 0), 0],&lt;br /&gt;
            [localLineXAt(corporationPoints[1], corporationPoints[2], 0), 0],&lt;br /&gt;
            [localLineXAt(corporationPoints[1], corporationPoints[2], ROW_HEIGHT), ROW_HEIGHT],&lt;br /&gt;
            [localLineXAt(settingsPoints[0], settingsPoints[3], ROW_HEIGHT), ROW_HEIGHT]&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        fallback = {&lt;br /&gt;
            leftDirection:[&lt;br /&gt;
                [lineXAtY(outer[0], outer[3], 0) + INSET_OFFSET, 0],&lt;br /&gt;
                [lineXAtY(outer[0], outer[3], ROW_HEIGHT - 1) + INSET_OFFSET, ROW_HEIGHT - 1]&lt;br /&gt;
            ],&lt;br /&gt;
            rightDirection:[&lt;br /&gt;
                [lineXAtY(outer[1], outer[2], 0) - INSET_OFFSET, 0],&lt;br /&gt;
                [lineXAtY(outer[1], outer[2], ROW_HEIGHT - 1) - INSET_OFFSET, ROW_HEIGHT - 1]&lt;br /&gt;
            ]&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            outer:outer,&lt;br /&gt;
            leftDirection:settingsInsetPoints.length === 2 &amp;amp;&amp;amp; settingsInsetPoints.every(function (point) {&lt;br /&gt;
                return Number.isFinite(point[0]) &amp;amp;&amp;amp; Number.isFinite(point[1]);&lt;br /&gt;
            }) ? [&lt;br /&gt;
                [localLineXAt(settingsInsetPoints[0], settingsInsetPoints[1], 0), 0],&lt;br /&gt;
                [localLineXAt(settingsInsetPoints[0], settingsInsetPoints[1], ROW_HEIGHT - 1), ROW_HEIGHT - 1]&lt;br /&gt;
            ] : fallback.leftDirection,&lt;br /&gt;
            rightDirection:corporationInsetPoints.length === 2 &amp;amp;&amp;amp; corporationInsetPoints.every(function (point) {&lt;br /&gt;
                return Number.isFinite(point[0]) &amp;amp;&amp;amp; Number.isFinite(point[1]);&lt;br /&gt;
            }) ? [&lt;br /&gt;
                [localLineXAt(corporationInsetPoints[0], corporationInsetPoints[1], 0), 0],&lt;br /&gt;
                [localLineXAt(corporationInsetPoints[0], corporationInsetPoints[1], ROW_HEIGHT - 1), ROW_HEIGHT - 1]&lt;br /&gt;
            ] : fallback.rightDirection&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendFrame(svg, outer, directions) {&lt;br /&gt;
        var surface = projectSidesToY(outer, 0, ROW_HEIGHT - 1, OUTER_BORDER);&lt;br /&gt;
        var well = projectSidesToY(outer, TOP_FRAME, ROW_HEIGHT - BOTTOM_FRAME, TOP_FRAME);&lt;br /&gt;
        var outline = projectSidesToY(outer, 0.5, ROW_HEIGHT - 0.5, 0.5);&lt;br /&gt;
        var innerDirection = projectSidesToY(outer, 1.5, ROW_HEIGHT - 1.5, 1.5);&lt;br /&gt;
        var group = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var joinDepth = TOP_FRAME;&lt;br /&gt;
        var leftJoinCover;&lt;br /&gt;
        var rightJoinCover;&lt;br /&gt;
        var leftDirection = [&lt;br /&gt;
            innerDirection[0],&lt;br /&gt;
            innerDirection[3]&lt;br /&gt;
        ];&lt;br /&gt;
        var rightDirection = [&lt;br /&gt;
            innerDirection[1],&lt;br /&gt;
            innerDirection[2]&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        group.setAttribute(&#039;class&#039;, &#039;portal-section-frame&#039;);&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-frame-edge&#039;, outer));&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-frame-surface&#039;, surface));&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-frame-well&#039;, well));&lt;br /&gt;
&lt;br /&gt;
        /* 우물의 수평 방향성은 좌우 접합 처리와 무관하므로 먼저 그린다. */&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-well-shadow&#039;, well[0], well[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-well-highlight&#039;, well[2], well[3]));&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * 기존 프레임과 포개지는 상단 9px은 같은 재료의 면이다.&lt;br /&gt;
         * 덮개는 외곽에서 우물 경계까지 정확히 채우되 우물 안으로 1px&lt;br /&gt;
         * 침범하지 않는다. 좌우 방향선은 덮개 뒤 y=9부터 직접 시작한다.&lt;br /&gt;
         */&lt;br /&gt;
        leftJoinCover = [&lt;br /&gt;
            [lineXAtY(outer[0], outer[3], 0) - OUTER_BORDER, 0],&lt;br /&gt;
            [lineXAtY(well[0], well[3], 0), 0],&lt;br /&gt;
            [lineXAtY(well[0], well[3], joinDepth), joinDepth],&lt;br /&gt;
            [lineXAtY(outer[0], outer[3], joinDepth) - OUTER_BORDER, joinDepth]&lt;br /&gt;
        ];&lt;br /&gt;
        rightJoinCover = [&lt;br /&gt;
            [lineXAtY(well[1], well[2], 0), 0],&lt;br /&gt;
            [lineXAtY(outer[1], outer[2], 0) + OUTER_BORDER, 0],&lt;br /&gt;
            [lineXAtY(outer[1], outer[2], joinDepth) + OUTER_BORDER, joinDepth],&lt;br /&gt;
            [lineXAtY(well[1], well[2], joinDepth), joinDepth]&lt;br /&gt;
        ];&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-join-side-cover&#039;, leftJoinCover));&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-join-side-cover&#039;, rightJoinCover));&lt;br /&gt;
&lt;br /&gt;
        /* #050505 외곽 1px과 그 안쪽 방향선 1px을 서로 다른 픽셀 열에 둔다. */&lt;br /&gt;
        group.appendChild(createPolygon(&#039;portal-section-frame-outline&#039;, outline));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-frame-shadow&#039;, rightDirection[1], leftDirection[1]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-frame-shadow&#039;, leftDirection[1], leftDirection[0]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-frame-highlight&#039;, rightDirection[0], rightDirection[1]));&lt;br /&gt;
&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-well-shadow&#039;, well[1], well[2]));&lt;br /&gt;
        group.appendChild(createLine(&#039;portal-section-well-highlight&#039;, well[3], well[0]));&lt;br /&gt;
        svg.appendChild(group);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function splitButtons(outer) {&lt;br /&gt;
        var union = projectSidesToY(outer, BUTTON_TOP, BUTTON_BOTTOM, TOP_FRAME + GAP);&lt;br /&gt;
        var topMid = (union[0][0] + union[1][0]) / 2;&lt;br /&gt;
        var bottomMid = (union[3][0] + union[2][0]) / 2;&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            [&lt;br /&gt;
                union[0],&lt;br /&gt;
                [topMid - GAP / 2, BUTTON_TOP],&lt;br /&gt;
                [bottomMid - GAP / 2, BUTTON_BOTTOM],&lt;br /&gt;
                union[3]&lt;br /&gt;
            ],&lt;br /&gt;
            [&lt;br /&gt;
                [topMid + GAP / 2, BUTTON_TOP],&lt;br /&gt;
                union[1],&lt;br /&gt;
                union[2],&lt;br /&gt;
                [bottomMid + GAP / 2, BUTTON_BOTTOM]&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function appendButton(svg, item, points) {&lt;br /&gt;
        var anchor = createSvgElement(&#039;a&#039;);&lt;br /&gt;
        var geometry = createSvgElement(&#039;g&#039;);&lt;br /&gt;
        var surfacePoints = projectSidesToY(&lt;br /&gt;
            points,&lt;br /&gt;
            points[0][1] + OUTER_BORDER,&lt;br /&gt;
            points[3][1] - OUTER_BORDER,&lt;br /&gt;
            OUTER_BORDER&lt;br /&gt;
        );&lt;br /&gt;
        var insetPoints = projectSidesToY(&lt;br /&gt;
            points,&lt;br /&gt;
            points[0][1] + INSET_OFFSET,&lt;br /&gt;
            points[3][1] - INSET_OFFSET,&lt;br /&gt;
            INSET_OFFSET&lt;br /&gt;
        );&lt;br /&gt;
        var center = getCentroid(surfacePoints);&lt;br /&gt;
        var text = createSvgElement(&#039;text&#039;);&lt;br /&gt;
        var hit = createPolygon(&#039;portal-cat-hit&#039;, points);&lt;br /&gt;
        var href = item.href || buildWikiPath(item.title);&lt;br /&gt;
&lt;br /&gt;
        anchor.setAttribute(&#039;class&#039;, &#039;portal-cat-anchor portal-section-anchor&#039;);&lt;br /&gt;
        anchor.setAttribute(&#039;data-category-key&#039;, item.key);&lt;br /&gt;
        anchor.setAttribute(&#039;href&#039;, href);&lt;br /&gt;
        anchor.setAttributeNS(XLINK_NS, &#039;xlink:href&#039;, href);&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;, item.label);&lt;br /&gt;
&lt;br /&gt;
        geometry.setAttribute(&#039;class&#039;, &#039;portal-section-button-geometry&#039;);&lt;br /&gt;
        geometry.appendChild(createPolygon(&#039;portal-cat-edge&#039;, points));&lt;br /&gt;
        geometry.appendChild(createPolygon(&#039;portal-cat-surface&#039;, surfacePoints));&lt;br /&gt;
        geometry.appendChild(createLine(&#039;portal-cat-inset portal-cat-inset-top&#039;, insetPoints[0], insetPoints[1]));&lt;br /&gt;
        geometry.appendChild(createLine(&#039;portal-cat-inset portal-cat-inset-right&#039;, insetPoints[1], insetPoints[2]));&lt;br /&gt;
        geometry.appendChild(createLine(&#039;portal-cat-inset portal-cat-inset-bottom&#039;, insetPoints[2], insetPoints[3]));&lt;br /&gt;
        geometry.appendChild(createLine(&#039;portal-cat-inset portal-cat-inset-left&#039;, insetPoints[3], insetPoints[0]));&lt;br /&gt;
        hit.setAttribute(&#039;pointer-events&#039;, &#039;all&#039;);&lt;br /&gt;
        geometry.appendChild(hit);&lt;br /&gt;
&lt;br /&gt;
        text.setAttribute(&#039;class&#039;, &#039;portal-cat-label portal-section-label&#039;);&lt;br /&gt;
        text.setAttribute(&#039;x&#039;, roundCoordinate(center[0]));&lt;br /&gt;
        text.setAttribute(&#039;y&#039;, roundCoordinate((BUTTON_TOP + BUTTON_BOTTOM) / 2 + 1));&lt;br /&gt;
        text.textContent = item.label;&lt;br /&gt;
&lt;br /&gt;
        anchor.appendChild(geometry);&lt;br /&gt;
        anchor.appendChild(text);&lt;br /&gt;
        svg.appendChild(anchor);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPortal(portal) {&lt;br /&gt;
        var topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        var mount;&lt;br /&gt;
        var rect;&lt;br /&gt;
        var width;&lt;br /&gt;
        var geometry;&lt;br /&gt;
        var outer;&lt;br /&gt;
        var buttons;&lt;br /&gt;
        var nav;&lt;br /&gt;
        var svg;&lt;br /&gt;
&lt;br /&gt;
        if (!topMount || !topMount.querySelector(&#039;.portal-category-svg&#039;)) return false;&lt;br /&gt;
        mount = ensureMount(portal, topMount);&lt;br /&gt;
        rect = mount.getBoundingClientRect();&lt;br /&gt;
        width = Math.floor(rect.width || topMount.getBoundingClientRect().width || 0);&lt;br /&gt;
        if (!width) return false;&lt;br /&gt;
&lt;br /&gt;
        geometry = readFrameGeometry(topMount, mount, width);&lt;br /&gt;
        outer = geometry.outer;&lt;br /&gt;
        buttons = splitButtons(outer);&lt;br /&gt;
        nav = document.createElement(&#039;nav&#039;);&lt;br /&gt;
        svg = createSvgElement(&#039;svg&#039;);&lt;br /&gt;
&lt;br /&gt;
        nav.className = &#039;portal-section-nav&#039;;&lt;br /&gt;
        nav.setAttribute(&#039;aria-label&#039;, &#039;작품 영역&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;class&#039;, &#039;portal-section-svg&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 &#039; + width + &#039; &#039; + ROW_HEIGHT);&lt;br /&gt;
        svg.setAttribute(&#039;width&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;height&#039;, &#039;100%&#039;);&lt;br /&gt;
        svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        appendFrame(svg, outer, geometry);&lt;br /&gt;
        ITEMS.forEach(function (item, index) {&lt;br /&gt;
            appendButton(svg, item, buttons[index]);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        nav.appendChild(svg);&lt;br /&gt;
        mount.replaceChildren(nav);&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-rendered&#039;, &#039;1&#039;);&lt;br /&gt;
        mount.setAttribute(&#039;data-portal-section-width&#039;, String(width));&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectPortals(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var portals = [];&lt;br /&gt;
&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(&#039;.main-portal&#039;)) portals.push(scope);&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;.main-portal&#039;), function (portal) {&lt;br /&gt;
            if (portals.indexOf(portal) === -1) portals.push(portal);&lt;br /&gt;
        });&lt;br /&gt;
        return portals;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderAll(root) {&lt;br /&gt;
        var rendered = 0;&lt;br /&gt;
        collectPortals(root).forEach(function (portal) {&lt;br /&gt;
            if (renderPortal(portal)) rendered += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return rendered;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleRender(root) {&lt;br /&gt;
        window.clearTimeout(renderTimer);&lt;br /&gt;
        renderTimer = window.setTimeout(function () {&lt;br /&gt;
            renderTimer = 0;&lt;br /&gt;
            renderAll(root || document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function observe() {&lt;br /&gt;
        if (observer || !window.MutationObserver || !document.documentElement) return;&lt;br /&gt;
        observer = new window.MutationObserver(function (records) {&lt;br /&gt;
            var relevant = records.some(function (record) {&lt;br /&gt;
                return Array.prototype.some.call(record.addedNodes || [], function (node) {&lt;br /&gt;
                    return node &amp;amp;&amp;amp; node.nodeType === 1 &amp;amp;&amp;amp; (&lt;br /&gt;
                        (node.matches &amp;amp;&amp;amp; node.matches(&#039;.portal-category-svg&#039;)) ||&lt;br /&gt;
                        (node.querySelector &amp;amp;&amp;amp; node.querySelector(&#039;.portal-category-svg&#039;))&lt;br /&gt;
                    );&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
            if (relevant) scheduleRender(document);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(document.documentElement, { childList:true, subtree:true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function init(root) {&lt;br /&gt;
        renderAll(root || document);&lt;br /&gt;
        observe();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
            scheduleRender(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () { init(document); });&lt;br /&gt;
    } else {&lt;br /&gt;
        init(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        window.clearTimeout(resizeTimer);&lt;br /&gt;
        resizeTimer = window.setTimeout(function () {&lt;br /&gt;
            resizeTimer = 0;&lt;br /&gt;
            renderAll(document);&lt;br /&gt;
        }, 80);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
window.PortalSectionNav = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        init:init,&lt;br /&gt;
        render:renderPortal,&lt;br /&gt;
        renderAll:renderAll&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4656</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4656"/>
		<updated>2026-07-17T00:46:15Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-separated-outline-effects-20260717 / MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:36px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
설정 왼쪽부터 기업 및 공동체 오른쪽까지의 실제 SVG 경계를 읽어&lt;br /&gt;
중앙 프레임의 최외곽 폭으로 사용한다. 좌우 투명 영역은 새 프레임으로&lt;br /&gt;
채우지 않는다. 형상은 위가 넓고 아래가 좁아지도록 뒤집고, 기존 상단&lt;br /&gt;
프레임 하단 9px 안으로 겹쳐 하나로 접합한다.&lt;br /&gt;
좌우 경사는 기존 설정·기업 버튼 사선의 화면상 기울기를 그대로 연장한다.&lt;br /&gt;
외곽·프레임 면·우물·버튼의 x 좌표도 같은 직선식으로 투영해 전 층의&lt;br /&gt;
사선이 서로 평행하고 끊김 없이 이어지게 한다.&lt;br /&gt;
두 프레임이 실제로 포개지는 상단 8px에서는 #050505를 #1d1d1d 면으로&lt;br /&gt;
덮는다. 좌우 방향선은 외곽선에서 재계산하지 않고 기존 설정 버튼의 좌측&lt;br /&gt;
인셋선과 기업 버튼의 우측 인셋선을 직접 읽어 같은 직선으로 연장한다.&lt;br /&gt;
&lt;br /&gt;
초기 길라잡이 규격:&lt;br /&gt;
- 버튼 외곽 22px&lt;br /&gt;
- 프레임 실색 #1d1d1d 7px&lt;br /&gt;
- 우물 간격 2px&lt;br /&gt;
- 전체 부착 행 42px&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-outline {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:10%;&lt;br /&gt;
    right:auto;&lt;br /&gt;
    width:80%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-svg {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 42px 본체·광원 + 상하 1px #050505 외곽선으로 44px이다.&lt;br /&gt;
   상하 2px #080808 간격, 상단 9px·하단 8px 프레임을 포함한 전체 행은 65px이다.&lt;br /&gt;
   상단 9px 중 외곽선·광원선을 제외한 #1d1d1d 실색 영역은 7px이다. */&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단 총 36px:&lt;br /&gt;
4px 프레임 + 2px #080808 + 24px 버튼 + 2px #080808 + 4px 프레임&lt;br /&gt;
&lt;br /&gt;
하단 총 65px:&lt;br /&gt;
중앙에서 위로 솟은 패널 하단 프레임 + 2px #080808&lt;br /&gt;
+ 44px 길라잡이 외곽 + 2px #080808 + 공유 하단 프레임&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:24px !important;&lt;br /&gt;
    min-height:24px !important;&lt;br /&gt;
    max-height:24px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:22px !important;&lt;br /&gt;
    min-height:22px !important;&lt;br /&gt;
    max-height:22px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4655</id>
		<title>미디어위키:Common.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4655"/>
		<updated>2026-07-17T00:46:15Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-separated-outline-effects-20260717 / Common.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;mw.loader.load(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* Raw MediaWiki script cache key.&lt;br /&gt;
   This is not a semantic version. It only prevents stale raw JS during active editing. */&lt;br /&gt;
window.CLBI_RAW_LOAD_BUST = &#039;portal-section-layered-outline-20260717-044&#039;;&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Raw script loads are tracked so the site boot gate can wait for real subsystem scripts&lt;br /&gt;
before releasing the normal surface.  The old prefixed load-bust variable remains for&lt;br /&gt;
compatibility only; new loader state uses unprefixed EntryScriptLoads/EntryRawScriptPromises.&lt;br /&gt;
*/&lt;br /&gt;
window.EntryScriptLoads = window.EntryScriptLoads || [];&lt;br /&gt;
window.EntryRawScriptPromises = window.EntryRawScriptPromises || {};&lt;br /&gt;
function buildEntryRawScriptUrl(key) {&lt;br /&gt;
    var url = &#039;/index.php?title=&#039; + encodeURIComponent(key) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;;&lt;br /&gt;
    var token;&lt;br /&gt;
    var sep;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, key);&lt;br /&gt;
        }&lt;br /&gt;
        if (/[?&amp;amp;]_entryRev=/.test(url)) return url;&lt;br /&gt;
        token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(&#039;raw-script-fallback&#039;) : window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        token = window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
    return url + sep + &#039;v=&#039; + encodeURIComponent(String(token || &#039;entry-raw&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadClbiRawScript(title) {&lt;br /&gt;
    var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
    var promise;&lt;br /&gt;
&lt;br /&gt;
    if (!key) return Promise.resolve(false);&lt;br /&gt;
    if (window.EntryRawScriptPromises[key]) return window.EntryRawScriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
    promise = Promise.resolve()&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.ensureLoaded === &#039;function&#039;) {&lt;br /&gt;
                return window.RevisionManifest.ensureLoaded();&lt;br /&gt;
            }&lt;br /&gt;
            return null;&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function () { return null; })&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
                script.src = buildEntryRawScriptUrl(key);&lt;br /&gt;
                script.async = true;&lt;br /&gt;
                script.setAttribute(&#039;data-entry-raw-script&#039;, key);&lt;br /&gt;
                script.onload = function () { resolve({ title: key, ok: true, src: script.src }); };&lt;br /&gt;
                script.onerror = function () { resolve({ title: key, ok: false, src: script.src }); };&lt;br /&gt;
                (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    window.EntryRawScriptPromises[key] = promise;&lt;br /&gt;
    window.EntryScriptLoads.push(promise);&lt;br /&gt;
    return promise;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Main-page fresh assets with fallback&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css의 MainPage.css import는 안전망으로 그대로 유지한다.&lt;br /&gt;
대문에서는 새로고침마다 timestamp가 붙은 CSS/JS를 추가로 불러와&lt;br /&gt;
브라우저·프록시의 오래된 raw 응답을 우회한다.&lt;br /&gt;
&lt;br /&gt;
이 보조 로더가 실패해도 기존 정적 import와 기존 화면은 남는다.&lt;br /&gt;
*/&lt;br /&gt;
window.MainPageFreshAssets = window.MainPageFreshAssets || (function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var sessionToken = String(Date.now());&lt;br /&gt;
    var ASSET_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var scriptPromises = {};&lt;br /&gt;
    var styleNode = null;&lt;br /&gt;
&lt;br /&gt;
    function isMainPage() {&lt;br /&gt;
        var page = &#039;&#039;;&lt;br /&gt;
        try {&lt;br /&gt;
            page = String(mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;);&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return page.replace(/_/g, &#039; &#039;) === &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrl(title, type) {&lt;br /&gt;
        var ctype = type === &#039;style&#039; ? &#039;text/css&#039; : &#039;text/javascript&#039;;&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) +&lt;br /&gt;
            &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype) +&lt;br /&gt;
            &#039;&amp;amp;_mainPageFresh=&#039; + encodeURIComponent(sessionToken);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadStyle() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        if (styleNode &amp;amp;&amp;amp; styleNode.parentNode) {&lt;br /&gt;
            return Promise.resolve(true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var link = document.createElement(&#039;link&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            link.rel = &#039;stylesheet&#039;;&lt;br /&gt;
            link.href = rawUrl(&#039;MediaWiki:MainPage.css&#039;, &#039;style&#039;);&lt;br /&gt;
            link.setAttribute(&#039;data-main-page-fresh-style&#039;, sessionToken);&lt;br /&gt;
            link.onload = function () {&lt;br /&gt;
                styleNode = link;&lt;br /&gt;
                settle(true);&lt;br /&gt;
            };&lt;br /&gt;
            link.onerror = function () {&lt;br /&gt;
                /* Common.css의 정적 import가 그대로 남아 있으므로 화면은 유지된다. */&lt;br /&gt;
                settle(false);&lt;br /&gt;
            };&lt;br /&gt;
            timeout = window.setTimeout(function () { settle(false); }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(link);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadScript(title) {&lt;br /&gt;
        var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!key || !isMainPage()) return Promise.resolve(false);&lt;br /&gt;
        if (scriptPromises[key]) return scriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
        scriptPromises[key] = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            script.src = rawUrl(key, &#039;script&#039;);&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-main-page-fresh-script&#039;, key);&lt;br /&gt;
            script.onload = function () { settle(true); };&lt;br /&gt;
            script.onerror = function () { settle(false); };&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return scriptPromises[key];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensure() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        return loadStyle()&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryPillar.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:PortalSectionNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:BottomGuideNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (window.CategoryNav &amp;amp;&amp;amp; typeof window.CategoryNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.CategoryPillar &amp;amp;&amp;amp; typeof window.CategoryPillar.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryPillar.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.PortalSectionNav &amp;amp;&amp;amp; typeof window.PortalSectionNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.PortalSectionNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.render === &#039;function&#039;) {&lt;br /&gt;
                    window.BottomGuideNav.render();&lt;br /&gt;
                }&lt;br /&gt;
                return true;&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function status() {&lt;br /&gt;
        return {&lt;br /&gt;
            mainPage: isMainPage(),&lt;br /&gt;
            token: sessionToken,&lt;br /&gt;
            style: !!document.querySelector(&#039;link[data-main-page-fresh-style]&#039;),&lt;br /&gt;
            category: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            pillar: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryPillar.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            section: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:PortalSectionNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            guide: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:BottomGuideNav.js&amp;quot;]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            ensure();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(ensure, 0);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        ensure: ensure,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Account access page marker&lt;br /&gt;
   =========================================&lt;br /&gt;
   New shared systems use unprefixed names.  The server body class differs&lt;br /&gt;
   between skins/locales, so the canonical special-page name is normalized&lt;br /&gt;
   once and exposed as a stable page-state class for the login surface.&lt;br /&gt;
*/&lt;br /&gt;
(function markAccountLoginPage(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var canonical = &#039;&#039;;&lt;br /&gt;
    var pageName = &#039;&#039;;&lt;br /&gt;
    var isLoginPage = false;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        canonical = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039; : &#039;&#039;).toLowerCase();&lt;br /&gt;
        pageName = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    isLoginPage = canonical === &#039;userlogin&#039; || /^(?:special|특수):(?:userlogin|로그인)$/.test(pageName);&lt;br /&gt;
    if (!isLoginPage) return;&lt;br /&gt;
&lt;br /&gt;
    document.documentElement.classList.add(&#039;account-login-page-root&#039;);&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountTarget(link) {&lt;br /&gt;
        var href = &#039;&#039;;&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var text = &#039;&#039;;&lt;br /&gt;
        var sample = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!link) return false;&lt;br /&gt;
        href = String(link.getAttribute(&#039;href&#039;) || &#039;&#039;);&lt;br /&gt;
        title = String(link.getAttribute(&#039;title&#039;) || &#039;&#039;);&lt;br /&gt;
        text = String(link.textContent || &#039;&#039;);&lt;br /&gt;
        try { href = decodeURIComponent(href); } catch (err) {}&lt;br /&gt;
        sample = (href + &#039; &#039; + title + &#039; &#039; + text).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        return /(?:special|특수)\s*[:%]\s*(?:createaccount|계정\s*(?:만들기|생성))|createaccount|계정\s*(?:만들기|생성)/i.test(sample);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function suppressCreateAccountSurface(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var fixedSelectors = [&lt;br /&gt;
            &#039;.mw-createacct-benefits-container&#039;,&lt;br /&gt;
            &#039;.mw-createacct-benefits-list&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-cta&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;#mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;.mw-userlogin-create&#039;&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        fixedSelectors.forEach(function (selector) {&lt;br /&gt;
            Array.prototype.forEach.call(scope.querySelectorAll(selector), function (node) {&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                node.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;a[href]&#039;), function (link) {&lt;br /&gt;
            var container;&lt;br /&gt;
            if (!isCreateAccountTarget(link)) return;&lt;br /&gt;
&lt;br /&gt;
            link.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            link.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            link.setAttribute(&#039;tabindex&#039;, &#039;-1&#039;);&lt;br /&gt;
&lt;br /&gt;
            container = link.closest(&#039;.mw-ui-vform-field, .oo-ui-fieldLayout, .mw-userlogin-create, p, li&#039;);&lt;br /&gt;
            if (container &amp;amp;&amp;amp; container.querySelectorAll(&#039;a&#039;).length === 1) {&lt;br /&gt;
                container.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                container.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyMarker() {&lt;br /&gt;
        if (!document.body) return;&lt;br /&gt;
        document.body.classList.add(&#039;account-login-page&#039;);&lt;br /&gt;
        suppressCreateAccountSurface(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, applyMarker, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyMarker();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                suppressCreateAccountSurface(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Boot gate prelude&lt;br /&gt;
   =========================================&lt;br /&gt;
   This tiny prelude runs before the full EntryStore/EntryLoader/BootGate implementation.&lt;br /&gt;
   Its job is only to guarantee that the user sees the separate loading surface before the&lt;br /&gt;
   normal wiki shell can paint.  The full BootGate later adopts the same DOM node and keeps&lt;br /&gt;
   it open until the real full-entry readiness contract is satisfied.&lt;br /&gt;
&lt;br /&gt;
   Maintenance rule:&lt;br /&gt;
   - Do not move first-screen hiding into a late subsystem callback.  If the boot gate is&lt;br /&gt;
     meant to protect the first impression, the surface must be activated before sidebars,&lt;br /&gt;
     navbars, document wells, nations panels, or other entry surfaces can visibly fill in.&lt;br /&gt;
   - New names remain unprefixed.  Existing prefixed names elsewhere are legacy aliases only.&lt;br /&gt;
*/&lt;br /&gt;
(function installBootGatePrelude(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SCREEN_ID = &#039;boot-gate-screen&#039;;&lt;br /&gt;
    var STYLE_ID = &#039;boot-gate-prelude-style&#039;;&lt;br /&gt;
    var START_TIME = Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getMwConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.mw &amp;amp;&amp;amp; window.mw.config &amp;amp;&amp;amp; typeof window.mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = window.mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDeveloperOrEditingPage() {&lt;br /&gt;
        var ns = Number(getMwConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(getMwConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(getMwConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Developer/system pages must stay practical.  Editing MediaWiki:, File:,&lt;br /&gt;
         * Project:, Template:, Module:, Category:, or any non-view action already forces&lt;br /&gt;
         * a full page load in MediaWiki, so showing the entry boot screen there only slows&lt;br /&gt;
         * maintenance work and does not improve the public first impression.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = getMwConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(getMwConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(getMwConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var anonymousUser = isAnonymousUser();&lt;br /&gt;
    var disabled = isAuthenticationPage() || (!anonymousUser &amp;amp;&amp;amp; (hasQueryFlag(&#039;bootGate&#039;, &#039;0&#039;) || isDeveloperOrEditingPage()));&lt;br /&gt;
&lt;br /&gt;
    function injectStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (disabled || document.getElementById(STYLE_ID)) return;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = STYLE_ID;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active,body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt;:not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen{position:fixed!important;inset:0!important;z-index:2147483000!important;display:flex!important;align-items:center!important;justify-content:center!important;background:#080808!important;color:#d8d8d8!important;opacity:1;pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-decoration-layer{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-panel{position:relative;z-index:2;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-close{display:none;position:absolute;right:7px;top:6px;z-index:3;height:18px;min-width:18px;border:1px solid #000;background:#141414;color:#ddd;font-size:11px;line-height:16px;padding:0 5px;cursor:pointer;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview{inset:auto!important;left:18px!important;top:18px!important;width:min(720px,calc(100vw - 380px))!important;height:360px!important;z-index:99990!important;overflow:hidden!important;border:1px solid #000!important;box-shadow:0 10px 28px rgba(0,0,0,.55)!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-panel,#boot-gate-screen.is-preview .boot-gate-close{pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-close{display:block;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activate() {&lt;br /&gt;
        if (disabled) return;&lt;br /&gt;
        injectStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeNode() {&lt;br /&gt;
        var node;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var status;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var fill;&lt;br /&gt;
        var progress;&lt;br /&gt;
        var detail;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
&lt;br /&gt;
        if (disabled || !document.body) return null;&lt;br /&gt;
        activate();&lt;br /&gt;
&lt;br /&gt;
        node = document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) return node;&lt;br /&gt;
&lt;br /&gt;
        node = document.createElement(&#039;div&#039;);&lt;br /&gt;
        node.id = SCREEN_ID;&lt;br /&gt;
        node.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
        node.setAttribute(&#039;data-boot-gate-prelude&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        title.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        status = document.createElement(&#039;div&#039;);&lt;br /&gt;
        status.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        status.textContent = &#039;Preparing site entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        fill = document.createElement(&#039;div&#039;);&lt;br /&gt;
        fill.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(fill);&lt;br /&gt;
&lt;br /&gt;
        progress = document.createElement(&#039;div&#039;);&lt;br /&gt;
        progress.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        progress.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        detail = document.createElement(&#039;div&#039;);&lt;br /&gt;
        detail.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        detail.textContent = &#039;boot gate prelude&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { release(node); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(title);&lt;br /&gt;
        panel.appendChild(status);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(progress);&lt;br /&gt;
        panel.appendChild(detail);&lt;br /&gt;
        node.appendChild(decoLayer);&lt;br /&gt;
        node.appendChild(panel);&lt;br /&gt;
        node.appendChild(close);&lt;br /&gt;
&lt;br /&gt;
        document.body.insertBefore(node, document.body.firstChild || null);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function onBody(callback) {&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            callback();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, callback, { once: true });&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function tick() {&lt;br /&gt;
            if (document.body) return callback();&lt;br /&gt;
            window.setTimeout(tick, 10);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function release(node) {&lt;br /&gt;
        node = node || document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) {&lt;br /&gt;
            node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
            node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            }, 240);&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (document.documentElement) document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    activate();&lt;br /&gt;
    onBody(function () {&lt;br /&gt;
        activate();&lt;br /&gt;
        makeNode();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.__BootGatePrelude = {&lt;br /&gt;
        startTime: START_TIME,&lt;br /&gt;
        activate: activate,&lt;br /&gt;
        ensure: makeNode,&lt;br /&gt;
        release: release,&lt;br /&gt;
        node: function () { return document.getElementById(SCREEN_ID); },&lt;br /&gt;
        disabled: disabled&lt;br /&gt;
    };&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Site boot gate and entry artifact contract&lt;br /&gt;
   =========================================&lt;br /&gt;
   This is the initial-load full/half entry artifact system.&lt;br /&gt;
&lt;br /&gt;
   Purpose:&lt;br /&gt;
   - The loading screen is not decorative. It exists only during the first site entry in a&lt;br /&gt;
     tab, before the user is allowed into the normal wiki surface.&lt;br /&gt;
   - A &amp;quot;full&amp;quot; entry must mean that the first view of that system can appear without an&lt;br /&gt;
     additional visible data load. For the nations system, the 1950 entry is full only when&lt;br /&gt;
     its nation list/link-map data and first-view pixel decorations are ready in this tab.&lt;br /&gt;
   - A &amp;quot;half&amp;quot; entry is a predictive warm state for the next likely path. It may fetch and&lt;br /&gt;
     parse data, but may skip expensive final work such as image/canvas preparation until it&lt;br /&gt;
     is promoted to full.&lt;br /&gt;
   - This principle is a site-wide design priority, like SPA continuity. New viewers,&lt;br /&gt;
     document systems, and information panels should define their entry full/half packs&lt;br /&gt;
     before exposing a first screen that can visibly fill in later.&lt;br /&gt;
   - SPA navigation is a consumer phase, not a blocking phase.  Do not add BootGate holds&lt;br /&gt;
     to SPA route changes.  If a route needs seamless first paint, prepare its artifacts&lt;br /&gt;
     during the initial boot pack and have the subsystem consume EntryStore synchronously&lt;br /&gt;
     before inserting or painting visible DOM.&lt;br /&gt;
&lt;br /&gt;
   Naming rule:&lt;br /&gt;
   - New public APIs, globals, classes, functions, files, and components must not use a&lt;br /&gt;
     project prefix. Old prefixed globals are legacy compatibility surfaces only.&lt;br /&gt;
   - New code should use names such as BootGate, EntryLoader, EntryStore, and boot-gate-*.&lt;br /&gt;
*/&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var MANIFEST_TITLE = &#039;MediaWiki:EntryManifest.json&#039;;&lt;br /&gt;
    var BUILD_ID = &#039;20260711-existing-account-login-001&#039;;&lt;br /&gt;
    var deferredEntryWarmups = [];&lt;br /&gt;
    var READY_KEY = &#039;boot-gate-ready-version&#039;;&lt;br /&gt;
    var DISMISS_PARAM = &#039;bootGate&#039;;&lt;br /&gt;
    var bootStarted = false;&lt;br /&gt;
    var bootPromise = null;&lt;br /&gt;
    var bootNode = null;&lt;br /&gt;
    var bootStatusNode = null;&lt;br /&gt;
    var bootProgressNode = null;&lt;br /&gt;
    var bootDetailNode = null;&lt;br /&gt;
    var bootFillNode = null;&lt;br /&gt;
    var bootStartTime = 0;&lt;br /&gt;
    var earlyBootStyleInjected = false;&lt;br /&gt;
    var loginGateLocked = false;&lt;br /&gt;
    var loginGateActionNode = null;&lt;br /&gt;
&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.config &amp;amp;&amp;amp; typeof mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBootPageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        return canonical === &#039;createaccount&#039; || /^(?:special|특수):(?:createaccount|계정 ?(?:만들기|생성))/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = readConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(readConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requiresLoginGate() {&lt;br /&gt;
        return isAnonymousUser() &amp;amp;&amp;amp; !isAuthenticationPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBootExcludedPage() {&lt;br /&gt;
        var ns = Number(readConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(readConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(readConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * The public boot gate is for normal reading surfaces.  Developer/system&lt;br /&gt;
         * namespaces and edit/diff/history actions already reload outside SPA, so&lt;br /&gt;
         * blocking them would turn every maintenance save into another entry boot.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var LOGIN_REQUIRED = requiresLoginGate();&lt;br /&gt;
    var BOOT_EXCLUDED_PAGE = isBootExcludedPage() &amp;amp;&amp;amp; !LOGIN_REQUIRED;&lt;br /&gt;
&lt;br /&gt;
    function injectEarlyBootStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (earlyBootStyleInjected || !document.documentElement) return;&lt;br /&gt;
        earlyBootStyleInjected = true;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = &#039;boot-gate-early-style&#039;;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active, body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt; :not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBootSurface() {&lt;br /&gt;
        injectEarlyBootStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!BOOT_EXCLUDED_PAGE) activateBootSurface();&lt;br /&gt;
    else if (window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.release) window.__BootGatePrelude.release();&lt;br /&gt;
&lt;br /&gt;
    var defaultManifest = {&lt;br /&gt;
        version: &#039;20260710-globe-vhs-restore-entry-001&#039;,&lt;br /&gt;
        boot: {&lt;br /&gt;
            minDisplayMs: 950,&lt;br /&gt;
            cachedMinDisplayMs: 350,&lt;br /&gt;
            maxBlockingMs: 15000&lt;br /&gt;
        },&lt;br /&gt;
        initial: {&lt;br /&gt;
            full: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;decorations-registry&#039;,&lt;br /&gt;
                    label: &#039;DECORATION REGISTRY&#039;,&lt;br /&gt;
                    type: &#039;decorations&#039;,&lt;br /&gt;
                    ref: &#039;MediaWiki:Decorations.json&#039;,&lt;br /&gt;
                    page: &#039;시대&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    preparePixels: true&lt;br /&gt;
                },&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1950-entry&#039;,&lt;br /&gt;
                    label: &#039;1950 ERA ENTRY&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    level: &#039;full&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ],&lt;br /&gt;
            half: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1960-half&#039;,&lt;br /&gt;
                    label: &#039;1960 ERA HALF&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1960&#039;,&lt;br /&gt;
                    level: &#039;half&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ]&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function now() {&lt;br /&gt;
        return Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var BootPerf = window.BootPerf = window.BootPerf || (function () {&lt;br /&gt;
        var t0 = now();&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;entry&#039;), start: now(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = now();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = ended - item.start;&lt;br /&gt;
            item.offset = item.start - t0;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            entries.push(item);&lt;br /&gt;
            return item;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function instant(name, meta) {&lt;br /&gt;
            var t = now();&lt;br /&gt;
            entries.push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: t - t0, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measure(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function rows() {&lt;br /&gt;
            return entries.slice().sort(function (a, b) { return a.start - b.start; }).map(function (item) {&lt;br /&gt;
                return {&lt;br /&gt;
                    offset: item.offset,&lt;br /&gt;
                    ms: item.ms,&lt;br /&gt;
                    name: item.name,&lt;br /&gt;
                    meta: item.meta || {}&lt;br /&gt;
                };&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            var total = list.reduce(function (max, item) { return Math.max(max, item.offset + item.ms); }, 0);&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD_ID,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: total,&lt;br /&gt;
                entries: list,&lt;br /&gt;
                active: Object.keys(active).map(function (id) { return active[id]; })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function print() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            if (!window.console || !console.log) return summary();&lt;br /&gt;
            try {&lt;br /&gt;
                console.groupCollapsed(&#039;[BootPerf] entry loading timeline · &#039; + BUILD_ID);&lt;br /&gt;
                if (console.table) console.table(list.map(function (item) {&lt;br /&gt;
                    return {&lt;br /&gt;
                        offset: item.offset + &#039;ms&#039;,&lt;br /&gt;
                        duration: item.ms + &#039;ms&#039;,&lt;br /&gt;
                        name: item.name,&lt;br /&gt;
                        detail: JSON.stringify(item.meta || {})&lt;br /&gt;
                    };&lt;br /&gt;
                }));&lt;br /&gt;
                else list.forEach(function (item) { console.log(item.offset + &#039;ms&#039;, item.ms + &#039;ms&#039;, item.name, item.meta || {}); });&lt;br /&gt;
                console.groupEnd();&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            return summary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: instant,&lt;br /&gt;
            measure: measure,&lt;br /&gt;
            rows: rows,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            print: print&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var InteractionPerf = window.InteractionPerf = window.InteractionPerf || (function () {&lt;br /&gt;
        var BUILD = &#039;20260710-interaction-perf-instrument-001&#039;;&lt;br /&gt;
        var t0 = (window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now());&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
        var maxEntries = 1600;&lt;br /&gt;
&lt;br /&gt;
        function perfNow() {&lt;br /&gt;
            return window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function push(entry) {&lt;br /&gt;
            entries.push(entry);&lt;br /&gt;
            if (entries.length &amp;gt; maxEntries) entries.splice(0, entries.length - maxEntries);&lt;br /&gt;
            return entry;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;interaction&#039;), start: perfNow(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = perfNow();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = Math.round((ended - item.start) * 100) / 100;&lt;br /&gt;
            item.offset = Math.round((item.start - t0) * 100) / 100;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            return push(item);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function mark(name, meta) {&lt;br /&gt;
            var t = perfNow();&lt;br /&gt;
            return push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: Math.round((t - t0) * 100) / 100, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureSync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                var value = fn();&lt;br /&gt;
                end(id, { ok: true });&lt;br /&gt;
                return value;&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                throw err;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureAsync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: Math.round((perfNow() - t0) * 100) / 100,&lt;br /&gt;
                entries: entries.slice().sort(function (a, b) {&lt;br /&gt;
                    if (a.offset !== b.offset) return a.offset - b.offset;&lt;br /&gt;
                    return b.ms - a.ms;&lt;br /&gt;
                }),&lt;br /&gt;
                active: Object.keys(active).map(function (id) {&lt;br /&gt;
                    var item = active[id];&lt;br /&gt;
                    return { offset: Math.round((item.start - t0) * 100) / 100, ms: Math.round((perfNow() - item.start) * 100) / 100, name: item.name, meta: item.meta || {} };&lt;br /&gt;
                })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function table() {&lt;br /&gt;
            var data = summary().entries.map(function (entry) {&lt;br /&gt;
                return { offset: entry.offset, ms: entry.ms, name: entry.name, meta: entry.meta };&lt;br /&gt;
            });&lt;br /&gt;
            if (window.console &amp;amp;&amp;amp; console.table) console.table(data);&lt;br /&gt;
            return data;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.PerformanceObserver &amp;amp;&amp;amp; !window.CLBI_InteractionLongTaskObserverBound) {&lt;br /&gt;
                window.CLBI_InteractionLongTaskObserverBound = true;&lt;br /&gt;
                new PerformanceObserver(function (list) {&lt;br /&gt;
                    list.getEntries().forEach(function (entry) {&lt;br /&gt;
                        mark(&#039;browser long task&#039;, {&lt;br /&gt;
                            ms: Math.round(entry.duration * 100) / 100,&lt;br /&gt;
                            start: Math.round(entry.startTime * 100) / 100,&lt;br /&gt;
                            attribution: entry.attribution &amp;amp;&amp;amp; entry.attribution.length ? entry.attribution.length : 0&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }).observe({ entryTypes: [&#039;longtask&#039;] });&lt;br /&gt;
            }&lt;br /&gt;
        } catch (ignoreLongTaskObserver) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD,&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: mark,&lt;br /&gt;
            measureSync: measureSync,&lt;br /&gt;
            measureAsync: measureAsync,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            table: table&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
    function toArray(value) {&lt;br /&gt;
        return Array.prototype.slice.call(value || []);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function unique(list) {&lt;br /&gt;
        var seen = {};&lt;br /&gt;
        var out = [];&lt;br /&gt;
        (list || []).forEach(function (item) {&lt;br /&gt;
            item = String(item || &#039;&#039;).trim();&lt;br /&gt;
            if (!item || seen[item]) return;&lt;br /&gt;
            seen[item] = true;&lt;br /&gt;
            out.push(item);&lt;br /&gt;
        });&lt;br /&gt;
        return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasBootParam(value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + DISMISS_PARAM + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeTitle(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;#&#039;)[0]&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function extractTitleFromUrl(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;);&lt;br /&gt;
        var match = text.match(/[?&amp;amp;]title=([^&amp;amp;]+)/i);&lt;br /&gt;
        if (match) return normalizeTitle(decodeURIComponent(match[1].replace(/\+/g, &#039; &#039;)));&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeRefKey(ref) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        title = extractTitleFromUrl(text);&lt;br /&gt;
        if (title) return &#039;title:&#039; + title.toLowerCase();&lt;br /&gt;
        if (text.indexOf(&#039;/&#039;) === -1 &amp;amp;&amp;amp; text.indexOf(&#039;:&#039;) !== -1) return &#039;title:&#039; + normalizeTitle(text).toLowerCase();&lt;br /&gt;
        return &#039;url:&#039; + text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrlForRef(ref, ctype) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(text) || text.charAt(0) === &#039;/&#039;) return text;&lt;br /&gt;
        title = normalizeTitle(text.indexOf(&#039;:&#039;) !== -1 ? text : (&#039;MediaWiki:&#039; + text));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            return mw.util.getUrl(title, { action: &#039;raw&#039;, ctype: ctype || &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype || &#039;application/json&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getApiEndpoint() {&lt;br /&gt;
        return (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.wikiScript === &#039;function&#039;) ? mw.util.wikiScript(&#039;api&#039;) : &#039;/api.php&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchApi(params) {&lt;br /&gt;
        var body = new URLSearchParams();&lt;br /&gt;
        Object.keys(params || {}).forEach(function (key) {&lt;br /&gt;
            body.append(key, params[key]);&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(getApiEndpoint(), {&lt;br /&gt;
            method: &#039;POST&#039;,&lt;br /&gt;
            credentials: &#039;same-origin&#039;,&lt;br /&gt;
            headers: { &#039;Content-Type&#039;: &#039;application/x-www-form-urlencoded; charset=UTF-8&#039; },&lt;br /&gt;
            body: body.toString()&lt;br /&gt;
        }).then(function (res) {&lt;br /&gt;
            if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
            return res.json();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =========================================&lt;br /&gt;
       Revision manifest and persistent entry cache&lt;br /&gt;
       =========================================&lt;br /&gt;
       The first boot still prepares current-tab artifacts, but freshness is not guessed from&lt;br /&gt;
       filenames or old localStorage flags.  The client reads a tiny server-side current-state&lt;br /&gt;
       manifest, compares page revisions / file sha1 values, and only keeps cached raw resources&lt;br /&gt;
       whose revision token still matches the server.  The manifest is a latest-state table, not&lt;br /&gt;
       an append-only client log.&lt;br /&gt;
    */&lt;br /&gt;
    var REVISION_MANIFEST_ACTION = &#039;entryrevisionmanifest&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_LOCAL_KEY = &#039;entry-revision-manifest-current-v1&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_PRUNE_LOCAL_KEY = &#039;entry-revision-manifest-last-client-prune-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_NAME = &#039;entry-cache-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_INDEX_KEY = &#039;entry-cache-index-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_REQUEST_PREFIX = &#039;/__entry-cache__/&#039;;&lt;br /&gt;
    var ENTRY_CACHE_PACK_KEY = &#039;entry-cache-pack-state-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
    function hasCacheStorage() {&lt;br /&gt;
        return !!(window.caches &amp;amp;&amp;amp; typeof window.caches.open === &#039;function&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function safeJsonParse(text, fallback) {&lt;br /&gt;
        try { return JSON.parse(text); } catch (err) { return fallback; }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readLocalJson(key, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            var text = window.localStorage ? window.localStorage.getItem(key) : null;&lt;br /&gt;
            return text ? safeJsonParse(text, fallback) : fallback;&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return fallback;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function writeLocalJson(key, value) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.localStorage) window.localStorage.setItem(key, JSON.stringify(value));&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeManifestTitle(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var match;&lt;br /&gt;
        var i;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        for (i = 0; i &amp;lt; 3; i += 1) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
            } catch (err) { break; }&lt;br /&gt;
        }&lt;br /&gt;
        match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
        if (match) text = match[1];&lt;br /&gt;
        text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^\/+/, &#039;&#039;)&lt;br /&gt;
            .replace(/^index\.php\/?/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^wiki\/?/i, &#039;&#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
        match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/([^?#]+)(?:[?#].*)?$/i);&lt;br /&gt;
        if (match) text = &#039;File:&#039; + match[1];&lt;br /&gt;
        text = text.split(&#039;#&#039;)[0].replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
        text = text.replace(/^(?:파일|Image|이미지)\s*:/i, &#039;File:&#039;);&lt;br /&gt;
        if (/^(?:mediawiki|template|module|manage|file)\s*:/i.test(text)) {&lt;br /&gt;
            text = text.replace(/^([^:]+)\s*:\s*/, function (_, ns) { return ns.charAt(0).toUpperCase() + ns.slice(1).toLowerCase() + &#039;:&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
        if (/^File:/i.test(text)) text = &#039;File:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        if (/^Mediawiki:/i.test(text)) text = &#039;MediaWiki:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        return text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resourceToken(resource) {&lt;br /&gt;
        if (!resource || typeof resource !== &#039;object&#039;) return &#039;&#039;;&lt;br /&gt;
        return String(resource.revision || resource.sha1 || resource.hash || resource.timestamp || resource.updatedAt || resource.url || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createEntryCache() {&lt;br /&gt;
        var index = readLocalJson(ENTRY_CACHE_INDEX_KEY, { entries: {} }) || { entries: {} };&lt;br /&gt;
        var packs = readLocalJson(ENTRY_CACHE_PACK_KEY, { packs: {} }) || { packs: {} };&lt;br /&gt;
        var objectUrls = {};&lt;br /&gt;
        var stats = {&lt;br /&gt;
            textHits: 0,&lt;br /&gt;
            blobHits: 0,&lt;br /&gt;
            misses: 0,&lt;br /&gt;
            networkStores: 0,&lt;br /&gt;
            stores: 0,&lt;br /&gt;
            deletes: 0&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        function ensureIndex() {&lt;br /&gt;
            if (!index || typeof index !== &#039;object&#039;) index = { entries: {} };&lt;br /&gt;
            if (!index.entries || typeof index.entries !== &#039;object&#039;) index.entries = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactIndexForLocalStorage() {&lt;br /&gt;
            var entries;&lt;br /&gt;
            var next = {};&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var entry = entries[key] || {};&lt;br /&gt;
                var kind = String(entry.kind || &#039;raw&#039;).toLowerCase();&lt;br /&gt;
                /*&lt;br /&gt;
                 * CacheStorage already owns image/blob bodies.  Keeping one localStorage&lt;br /&gt;
                 * metadata row per flag/blob competes with MediaWiki ResourceLoader&#039;s own&lt;br /&gt;
                 * localStorage module store and can trigger QuotaExceededError before our&lt;br /&gt;
                 * code even runs on the next page load.  Text/json/file URL rows stay indexed&lt;br /&gt;
                 * because they are small and useful for invalidation diagnostics.&lt;br /&gt;
                 */&lt;br /&gt;
                if (kind === &#039;image&#039; || kind === &#039;blob&#039;) return;&lt;br /&gt;
                next[key] = entry;&lt;br /&gt;
            });&lt;br /&gt;
            index.entries = next;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensurePacks() {&lt;br /&gt;
            if (!packs || typeof packs !== &#039;object&#039;) packs = { packs: {} };&lt;br /&gt;
            if (!packs.packs || typeof packs.packs !== &#039;object&#039;) packs.packs = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function saveIndex() {&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            compactIndexForLocalStorage();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_INDEX_KEY, index);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function savePacks() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_PACK_KEY, packs);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function requestForKey(key) {&lt;br /&gt;
            return new Request(ENTRY_CACHE_REQUEST_PREFIX + encodeURIComponent(String(key || &#039;&#039;)), { credentials: &#039;same-origin&#039; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function openCache() {&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return window.caches.open(ENTRY_CACHE_NAME).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheEntry(key, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            index.entries[key] = Object.assign({}, index.entries[key] || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                resourceKey: String(meta &amp;amp;&amp;amp; meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                token: String(meta &amp;amp;&amp;amp; meta.token || &#039;&#039;),&lt;br /&gt;
                kind: String(meta &amp;amp;&amp;amp; meta.kind || &#039;raw&#039;),&lt;br /&gt;
                contentType: String(meta &amp;amp;&amp;amp; meta.contentType || &#039;&#039;),&lt;br /&gt;
                size: Number(meta &amp;amp;&amp;amp; meta.size || 0) || null,&lt;br /&gt;
                createdAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].createdAt ? index.entries[key].createdAt : now(),&lt;br /&gt;
                updatedAt: now(),&lt;br /&gt;
                lastHitAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].lastHitAt ? index.entries[key].lastHitAt : null,&lt;br /&gt;
                hits: Number(index.entries[key] &amp;amp;&amp;amp; index.entries[key].hits || 0)&lt;br /&gt;
            });&lt;br /&gt;
            saveIndex();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function markHit(key, field) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            if (index.entries[key]) {&lt;br /&gt;
                index.entries[key].hits = Number(index.entries[key].hits || 0) + 1;&lt;br /&gt;
                index.entries[key].lastHitAt = now();&lt;br /&gt;
                saveIndex();&lt;br /&gt;
            }&lt;br /&gt;
            if (field &amp;amp;&amp;amp; stats[field] != null) stats[field] += 1;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getResponse(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return null;&lt;br /&gt;
                return cache.match(requestForKey(key)).then(function (res) {&lt;br /&gt;
                    if (!res) {&lt;br /&gt;
                        stats.misses += 1;&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }&lt;br /&gt;
                    return res;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putResponse(key, response, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !response || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var cloned;&lt;br /&gt;
                var headers;&lt;br /&gt;
                var bodyPromise;&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                cloned = response.clone();&lt;br /&gt;
                bodyPromise = cloned.blob().catch(function () { return null; });&lt;br /&gt;
                return bodyPromise.then(function (blob) {&lt;br /&gt;
                    if (!blob) return false;&lt;br /&gt;
                    headers = new Headers(response.headers || {});&lt;br /&gt;
                    if (!headers.get(&#039;Content-Type&#039;)) headers.set(&#039;Content-Type&#039;, meta.contentType || blob.type || &#039;application/octet-stream&#039;);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cache-Key&#039;, key);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Resource-Key&#039;, String(meta.resourceKey || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Revision-Token&#039;, String(meta.token || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cached-At&#039;, String(now()));&lt;br /&gt;
                    return cache.put(requestForKey(key), new Response(blob, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                        stats.stores += 1;&lt;br /&gt;
                        cacheEntry(key, {&lt;br /&gt;
                            resourceKey: meta.resourceKey,&lt;br /&gt;
                            token: meta.token,&lt;br /&gt;
                            kind: meta.kind || &#039;blob&#039;,&lt;br /&gt;
                            contentType: headers.get(&#039;Content-Type&#039;) || blob.type || &#039;&#039;,&lt;br /&gt;
                            size: blob.size&lt;br /&gt;
                        });&lt;br /&gt;
                        return true;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getText(key) {&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return null;&lt;br /&gt;
                markHit(key, &#039;textHits&#039;);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putText(key, text, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var headers;&lt;br /&gt;
                var body = String(text || &#039;&#039;);&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                headers = new Headers({&lt;br /&gt;
                    &#039;Content-Type&#039;: meta.contentType || &#039;text/plain; charset=UTF-8&#039;,&lt;br /&gt;
                    &#039;X-Entry-Cache-Key&#039;: key,&lt;br /&gt;
                    &#039;X-Entry-Resource-Key&#039;: String(meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Revision-Token&#039;: String(meta.token || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Cached-At&#039;: String(now())&lt;br /&gt;
                });&lt;br /&gt;
                return cache.put(requestForKey(key), new Response(body, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                    stats.stores += 1;&lt;br /&gt;
                    cacheEntry(key, {&lt;br /&gt;
                        resourceKey: meta.resourceKey,&lt;br /&gt;
                        token: meta.token,&lt;br /&gt;
                        kind: meta.kind || &#039;text&#039;,&lt;br /&gt;
                        contentType: headers.get(&#039;Content-Type&#039;),&lt;br /&gt;
                        size: body.length&lt;br /&gt;
                    });&lt;br /&gt;
                    return true;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getBlobUrl(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                return Promise.resolve(objectUrls[key]);&lt;br /&gt;
            }&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return &#039;&#039;;&lt;br /&gt;
                return res.blob().then(function (blob) {&lt;br /&gt;
                    if (!blob || !blob.size) return &#039;&#039;;&lt;br /&gt;
                    objectUrls[key] = URL.createObjectURL(blob);&lt;br /&gt;
                    markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                    return objectUrls[key];&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchBlobUrl(url, key, meta, options) {&lt;br /&gt;
            url = String(url || &#039;&#039;).trim();&lt;br /&gt;
            key = String(key || &#039;&#039;).trim();&lt;br /&gt;
            if (!url || !key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            return getBlobUrl(key).then(function (cachedUrl) {&lt;br /&gt;
                if (cachedUrl) return cachedUrl;&lt;br /&gt;
                return fetch(url, {&lt;br /&gt;
                    credentials: &#039;same-origin&#039;,&lt;br /&gt;
                    cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
                }).then(function (res) {&lt;br /&gt;
                    if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                    stats.networkStores += 1;&lt;br /&gt;
                    return putResponse(key, res, Object.assign({}, meta || {}, { kind: meta &amp;amp;&amp;amp; meta.kind ? meta.kind : &#039;blob&#039; })).then(function () {&lt;br /&gt;
                        return getBlobUrl(key);&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return &#039;&#039;;&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function deleteKey(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
                delete objectUrls[key];&lt;br /&gt;
            }&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                return cache.delete(requestForKey(key)).then(function (ok) {&lt;br /&gt;
                    ensureIndex();&lt;br /&gt;
                    if (index.entries &amp;amp;&amp;amp; index.entries[key]) {&lt;br /&gt;
                        delete index.entries[key];&lt;br /&gt;
                        stats.deletes += 1;&lt;br /&gt;
                        saveIndex();&lt;br /&gt;
                    }&lt;br /&gt;
                    return ok;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function invalidateResources(resourceKeys) {&lt;br /&gt;
            var map = {};&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys = [];&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            (resourceKeys || []).forEach(function (resourceKey) {&lt;br /&gt;
                resourceKey = String(resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (resourceKey) map[resourceKey] = true;&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(entries).forEach(function (cacheKey) {&lt;br /&gt;
                var resourceKey = String(entries[cacheKey] &amp;amp;&amp;amp; entries[cacheKey].resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (map[resourceKey]) keys.push(cacheKey);&lt;br /&gt;
            });&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function prune(maxAgeDays) {&lt;br /&gt;
            var days = Math.max(1, Number(maxAgeDays) || 7);&lt;br /&gt;
            var cutoff = now() - days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys;&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            keys = Object.keys(entries).filter(function (key) {&lt;br /&gt;
                return Number(entries[key] &amp;amp;&amp;amp; entries[key].createdAt || 0) &amp;lt; cutoff;&lt;br /&gt;
            });&lt;br /&gt;
            window.localStorage &amp;amp;&amp;amp; window.localStorage.setItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY, String(now()));&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function reset() {&lt;br /&gt;
            Object.keys(objectUrls).forEach(function (key) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
            });&lt;br /&gt;
            objectUrls = {};&lt;br /&gt;
            index = { entries: {} };&lt;br /&gt;
            packs = { packs: {} };&lt;br /&gt;
            saveIndex();&lt;br /&gt;
            savePacks();&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            return window.caches.delete(ENTRY_CACHE_NAME).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packReady(key, token) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            return !!(packs.packs[key] &amp;amp;&amp;amp; String(packs.packs[key].token || &#039;&#039;) === token &amp;amp;&amp;amp; packs.packs[key].ready);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setPackReady(key, token, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            packs.packs[key] = Object.assign({}, meta || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                token: token,&lt;br /&gt;
                ready: true,&lt;br /&gt;
                updatedAt: now()&lt;br /&gt;
            });&lt;br /&gt;
            savePacks();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packInfo() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            return Object.assign({}, packs.packs || {});&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function info() {&lt;br /&gt;
            var lastClientPrune = 0;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var byKind = {};&lt;br /&gt;
            try { lastClientPrune = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var kind = String(entries[key] &amp;amp;&amp;amp; entries[key].kind || &#039;raw&#039;);&lt;br /&gt;
                byKind[kind] = (byKind[kind] || 0) + 1;&lt;br /&gt;
            });&lt;br /&gt;
            return {&lt;br /&gt;
                supported: hasCacheStorage(),&lt;br /&gt;
                name: ENTRY_CACHE_NAME,&lt;br /&gt;
                entries: Object.keys(entries).length,&lt;br /&gt;
                byKind: byKind,&lt;br /&gt;
                packs: Object.keys(packs.packs || {}).length,&lt;br /&gt;
                lastClientPruneAt: lastClientPrune || null,&lt;br /&gt;
                stats: Object.assign({}, stats)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Trim legacy image/blob metadata rows produced by older builds. */&lt;br /&gt;
        try { saveIndex(); } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            getResponse: getResponse,&lt;br /&gt;
            putResponse: putResponse,&lt;br /&gt;
            getText: getText,&lt;br /&gt;
            putText: putText,&lt;br /&gt;
            getBlobUrl: getBlobUrl,&lt;br /&gt;
            fetchBlobUrl: fetchBlobUrl,&lt;br /&gt;
            invalidateResources: invalidateResources,&lt;br /&gt;
            prune: prune,&lt;br /&gt;
            reset: reset,&lt;br /&gt;
            packReady: packReady,&lt;br /&gt;
            setPackReady: setPackReady,&lt;br /&gt;
            packInfo: packInfo,&lt;br /&gt;
            info: info&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryCache = window.EntryCache || createEntryCache();&lt;br /&gt;
&lt;br /&gt;
    function createRevisionManifestService() {&lt;br /&gt;
        var current = null;&lt;br /&gt;
        var previous = readLocalJson(REVISION_MANIFEST_LOCAL_KEY, null);&lt;br /&gt;
        var loadPromise = null;&lt;br /&gt;
        var changedResources = [];&lt;br /&gt;
&lt;br /&gt;
        function unwrap(payload) {&lt;br /&gt;
            return payload &amp;amp;&amp;amp; (payload.entryrevisionmanifest || payload.revisionManifest || payload) || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourcesOf(manifest) {&lt;br /&gt;
            return manifest &amp;amp;&amp;amp; manifest.resources &amp;amp;&amp;amp; typeof manifest.resources === &#039;object&#039; ? manifest.resources : {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactRevisionManifest(manifest) {&lt;br /&gt;
            var out;&lt;br /&gt;
            var resources;&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039;) return null;&lt;br /&gt;
            out = {&lt;br /&gt;
                manifestVersion: manifest.manifestVersion || manifest.version || &#039;&#039;,&lt;br /&gt;
                version: manifest.version || manifest.manifestVersion || &#039;&#039;,&lt;br /&gt;
                generatedAt: manifest.generatedAt || &#039;&#039;,&lt;br /&gt;
                pruneDays: manifest.pruneDays,&lt;br /&gt;
                clientPruneDays: manifest.clientPruneDays,&lt;br /&gt;
                resources: {}&lt;br /&gt;
            };&lt;br /&gt;
            resources = resourcesOf(manifest);&lt;br /&gt;
            Object.keys(resources).forEach(function (title) {&lt;br /&gt;
                var src = resources[title];&lt;br /&gt;
                var dst;&lt;br /&gt;
                if (!src || typeof src !== &#039;object&#039;) return;&lt;br /&gt;
                dst = {};&lt;br /&gt;
                [&#039;revision&#039;, &#039;sha1&#039;, &#039;hash&#039;, &#039;timestamp&#039;, &#039;updatedAt&#039;, &#039;url&#039;].forEach(function (key) {&lt;br /&gt;
                    if (src[key] !== undefined &amp;amp;&amp;amp; src[key] !== null &amp;amp;&amp;amp; String(src[key]) !== &#039;&#039;) dst[key] = src[key];&lt;br /&gt;
                });&lt;br /&gt;
                out.resources[title] = dst;&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function buildLookup(manifest) {&lt;br /&gt;
            var lookup = {};&lt;br /&gt;
            Object.keys(resourcesOf(manifest)).forEach(function (title) {&lt;br /&gt;
                lookup[normalizeManifestTitle(title).toLowerCase()] = resourcesOf(manifest)[title];&lt;br /&gt;
            });&lt;br /&gt;
            return lookup;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function computeChanged(prev, next) {&lt;br /&gt;
            var prevLookup = buildLookup(prev);&lt;br /&gt;
            var nextLookup = buildLookup(next);&lt;br /&gt;
            var out = [];&lt;br /&gt;
            Object.keys(nextLookup).forEach(function (key) {&lt;br /&gt;
                if (resourceToken(prevLookup[key]) !== resourceToken(nextLookup[key])) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(prevLookup).forEach(function (key) {&lt;br /&gt;
                if (!nextLookup[key]) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            return unique(out);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function load(options) {&lt;br /&gt;
            if (loadPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return loadPromise;&lt;br /&gt;
            loadPromise = fetchApi({&lt;br /&gt;
                action: REVISION_MANIFEST_ACTION,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;&lt;br /&gt;
            }).then(function (payload) {&lt;br /&gt;
                var manifest = unwrap(payload);&lt;br /&gt;
                if (!manifest || !manifest.resources) throw new Error(&#039;invalid revision manifest&#039;);&lt;br /&gt;
                current = compactRevisionManifest(manifest) || manifest;&lt;br /&gt;
                changedResources = computeChanged(previous, current);&lt;br /&gt;
                if (changedResources.length &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.invalidateResources === &#039;function&#039;) {&lt;br /&gt;
                    window.EntryCache.invalidateResources(changedResources);&lt;br /&gt;
                }&lt;br /&gt;
                writeLocalJson(REVISION_MANIFEST_LOCAL_KEY, current);&lt;br /&gt;
                previous = current;&lt;br /&gt;
                maybePrune();&lt;br /&gt;
                return current;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                current = previous || null;&lt;br /&gt;
                return current;&lt;br /&gt;
            });&lt;br /&gt;
            return loadPromise;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensureLoaded() {&lt;br /&gt;
            return current ? Promise.resolve(current) : load();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            var lookup;&lt;br /&gt;
            if (!title || !current) return null;&lt;br /&gt;
            lookup = buildLookup(current);&lt;br /&gt;
            return lookup[title.toLowerCase()] || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenForRef(ref) {&lt;br /&gt;
            return resourceToken(resourceForRef(ref));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceKeyForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            return title ? title.toLowerCase() : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForRef(ref, type) {&lt;br /&gt;
            var resourceKey = resourceKeyForRef(ref);&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            if (!resourceKey || !token) return &#039;&#039;;&lt;br /&gt;
            return String(type || &#039;raw&#039;) + &#039;:&#039; + resourceKey + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function manifestToken(extra) {&lt;br /&gt;
            var base = current &amp;amp;&amp;amp; (current.manifestVersion || current.version || current.generatedAt || &#039;&#039;) || &#039;&#039;;&lt;br /&gt;
            return String(base || &#039;no-manifest&#039;) + (extra ? (&#039;:&#039; + String(extra)) : &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenFromUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;);&lt;br /&gt;
            var match = text.match(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=([^&amp;amp;]+)/);&lt;br /&gt;
            if (!match) return &#039;&#039;;&lt;br /&gt;
            try { return decodeURIComponent(match[1]); } catch (err) { return match[1]; }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForUrl(url, type) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var token = tokenFromUrl(text) || manifestToken(&#039;url&#039;);&lt;br /&gt;
            var normalized;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                normalized = new URL(text, window.location.href);&lt;br /&gt;
                text = normalized.pathname + (normalized.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            text = text.replace(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=[^&amp;amp;]*/g, &#039;&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            try { text = decodeURI(text); } catch (err2) {}&lt;br /&gt;
            return String(type || &#039;blob&#039;) + &#039;:url:&#039; + text.toLowerCase() + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function addRevisionParam(url, ref) {&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var sep;&lt;br /&gt;
            if (!text || !token || /[?&amp;amp;]_entryRev=/.test(text)) return text;&lt;br /&gt;
            sep = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + sep + &#039;_entryRev=&#039; + encodeURIComponent(token);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function maybePrune() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var due;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            due = !last || (now() - last &amp;gt;= days * 24 * 60 * 60 * 1000);&lt;br /&gt;
            if (due &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.prune === &#039;function&#039;) {&lt;br /&gt;
                window.EntryCache.prune(days);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function countdown() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var next;&lt;br /&gt;
            var remain;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            if (!last) return { days: days, lastClientPruneAt: null, nextClientPruneAt: null, remainingMs: 0, remainingDays: 0, due: true };&lt;br /&gt;
            next = last + days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            remain = Math.max(0, next - now());&lt;br /&gt;
            return {&lt;br /&gt;
                days: days,&lt;br /&gt;
                lastClientPruneAt: last,&lt;br /&gt;
                nextClientPruneAt: next,&lt;br /&gt;
                remainingMs: remain,&lt;br /&gt;
                remainingDays: Math.ceil(remain / (24 * 60 * 60 * 1000)),&lt;br /&gt;
                due: remain &amp;lt;= 0&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function status() {&lt;br /&gt;
            return {&lt;br /&gt;
                available: !!current,&lt;br /&gt;
                manifestVersion: current &amp;amp;&amp;amp; (current.manifestVersion || current.version || &#039;&#039;),&lt;br /&gt;
                generatedAt: current &amp;amp;&amp;amp; current.generatedAt || &#039;&#039;,&lt;br /&gt;
                changedResources: changedResources.slice(0, 50),&lt;br /&gt;
                changedCount: changedResources.length,&lt;br /&gt;
                prune: countdown(),&lt;br /&gt;
                cache: window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.info === &#039;function&#039; ? window.EntryCache.info() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            load: load,&lt;br /&gt;
            ensureLoaded: ensureLoaded,&lt;br /&gt;
            current: function () { return current; },&lt;br /&gt;
            resourceForRef: resourceForRef,&lt;br /&gt;
            tokenForRef: tokenForRef,&lt;br /&gt;
            cacheKeyForRef: cacheKeyForRef,&lt;br /&gt;
            cacheKeyForUrl: cacheKeyForUrl,&lt;br /&gt;
            manifestToken: manifestToken,&lt;br /&gt;
            resourceKeyForRef: resourceKeyForRef,&lt;br /&gt;
            addRevisionParam: addRevisionParam,&lt;br /&gt;
            maybePrune: maybePrune,&lt;br /&gt;
            countdown: countdown,&lt;br /&gt;
            status: status&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.RevisionManifest = window.RevisionManifest || createRevisionManifestService();&lt;br /&gt;
&lt;br /&gt;
    function createEntryStore() {&lt;br /&gt;
        var jsonCache = {};&lt;br /&gt;
        var flagUrlCache = {};&lt;br /&gt;
        var fileUrlCache = {};&lt;br /&gt;
        var imageReadyCache = {};&lt;br /&gt;
        var imageObjectCache = {};&lt;br /&gt;
        var imageDisplayUrlCache = {};&lt;br /&gt;
        var imagePromiseCache = {};&lt;br /&gt;
        var rawPromiseCache = {};&lt;br /&gt;
        var textCache = {};&lt;br /&gt;
        var textPromiseCache = {};&lt;br /&gt;
&lt;br /&gt;
        function fetchJsonRef(ref, options) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var promiseKey;&lt;br /&gt;
            var url;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty json ref&#039;));&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(ref, &#039;json&#039;) : &#039;&#039;;&lt;br /&gt;
            promiseKey = cacheKey ? (key + &#039;@&#039; + cacheKey) : key;&lt;br /&gt;
            if (jsonCache[promiseKey]) return Promise.resolve(jsonCache[promiseKey].data);&lt;br /&gt;
            if (rawPromiseCache[promiseKey]) return rawPromiseCache[promiseKey];&lt;br /&gt;
            url = rawUrlForRef(ref, &#039;application/json&#039;);&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
            }&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(ref) : key;&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(ref) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                rawPromiseCache[promiseKey] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        var cachedData = cachedText &amp;amp;&amp;amp; cachedText.trim() ? JSON.parse(cachedText) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: cachedData, loadedAt: now(), persistent: true };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        return cachedData;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token });&lt;br /&gt;
                        return data;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return rawPromiseCache[promiseKey];&lt;br /&gt;
            }&lt;br /&gt;
            rawPromiseCache[promiseKey] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                return data;&lt;br /&gt;
            });&lt;br /&gt;
            return rawPromiseCache[promiseKey];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getJsonSync(ref) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            return key &amp;amp;&amp;amp; jsonCache[key] ? jsonCache[key].data : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setJsonRef(ref, data) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            jsonCache[key] = { key: key, ref: ref, url: rawUrlForRef(ref, &#039;application/json&#039;), data: data, loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeFile(value) {&lt;br /&gt;
            return String(value || &#039;&#039;)&lt;br /&gt;
                .replace(/^(?:file|파일):/i, &#039;&#039;)&lt;br /&gt;
                .trim();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileKey(value) {&lt;br /&gt;
            return normalizeFile(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setFlagUrl(file, url) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            flagUrlCache[key] = String(url || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getFlagUrl(file) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            return key ? (flagUrlCache[key] || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFlagUrls(files) {&lt;br /&gt;
            var clean = unique((files || []).map(normalizeFile).filter(Boolean));&lt;br /&gt;
&lt;br /&gt;
            function cacheKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;flagurl&#039;) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function resourceKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + fileKey(file));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function tokenForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function hydrateCachedFlagUrl(file) {&lt;br /&gt;
                var key = fileKey(file);&lt;br /&gt;
                var cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                if (!key || flagUrlCache[key] !== undefined || !cacheKey || !window.EntryCache || typeof window.EntryCache.getText !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.resolve(false);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        flagUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return true;&lt;br /&gt;
                    }&lt;br /&gt;
                    return false;&lt;br /&gt;
                }).catch(function () { return false; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(clean.map(hydrateCachedFlagUrl)).then(function () {&lt;br /&gt;
                var pending = clean.filter(function (file) {&lt;br /&gt;
                    return getFlagUrl(file) === &#039;&#039; &amp;amp;&amp;amp; flagUrlCache[fileKey(file)] === undefined;&lt;br /&gt;
                });&lt;br /&gt;
                var chunks = [];&lt;br /&gt;
&lt;br /&gt;
                while (pending.length) chunks.push(pending.splice(0, 20));&lt;br /&gt;
                if (!chunks.length) return flagUrlCache;&lt;br /&gt;
&lt;br /&gt;
                return Promise.all(chunks.map(function (chunk) {&lt;br /&gt;
                    var titleToKey = {};&lt;br /&gt;
                    var keyToFile = {};&lt;br /&gt;
                    var titles = [];&lt;br /&gt;
                    chunk.forEach(function (file) {&lt;br /&gt;
                        var key = fileKey(file);&lt;br /&gt;
                        if (!key) return;&lt;br /&gt;
                        titleToKey[fileKey(&#039;File:&#039; + file)] = key;&lt;br /&gt;
                        titleToKey[fileKey(&#039;파일:&#039; + file)] = key;&lt;br /&gt;
                        keyToFile[key] = file;&lt;br /&gt;
                        titles.push(&#039;File:&#039; + file);&lt;br /&gt;
                        titles.push(&#039;파일:&#039; + file);&lt;br /&gt;
                    });&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size&#039;,&lt;br /&gt;
                        iiurlwidth: &#039;16&#039;,&lt;br /&gt;
                        titles: titles.join(&#039;|&#039;)&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var seen = {};&lt;br /&gt;
                        pages.forEach(function (page) {&lt;br /&gt;
                            var key = titleToKey[fileKey(page &amp;amp;&amp;amp; page.title)] || fileKey(page &amp;amp;&amp;amp; page.title);&lt;br /&gt;
                            var file = keyToFile[key] || (page &amp;amp;&amp;amp; page.title || &#039;&#039;).replace(/^(?:File|파일):/i, &#039;&#039;);&lt;br /&gt;
                            var imageinfo = page &amp;amp;&amp;amp; page.imageinfo &amp;amp;&amp;amp; page.imageinfo[0];&lt;br /&gt;
                            var url;&lt;br /&gt;
                            var rev;&lt;br /&gt;
                            var sep;&lt;br /&gt;
                            var cacheKey;&lt;br /&gt;
                            if (!key) return;&lt;br /&gt;
                            seen[key] = true;&lt;br /&gt;
                            url = imageinfo &amp;amp;&amp;amp; (imageinfo.thumburl || imageinfo.url) ? (imageinfo.thumburl || imageinfo.url) : &#039;&#039;;&lt;br /&gt;
                            if (url) {&lt;br /&gt;
                                rev = imageinfo &amp;amp;&amp;amp; (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) ? (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) : &#039;&#039;;&lt;br /&gt;
                                if (rev &amp;amp;&amp;amp; !/[?&amp;amp;]_entryFileRev=/.test(url)) {&lt;br /&gt;
                                    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                                    url += sep + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                                }&lt;br /&gt;
                            }&lt;br /&gt;
                            flagUrlCache[key] = url;&lt;br /&gt;
                            cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.putText === &#039;function&#039;) {&lt;br /&gt;
                                window.EntryCache.putText(cacheKey, url || &#039;&#039;, {&lt;br /&gt;
                                    resourceKey: resourceKeyForFile(file),&lt;br /&gt;
                                    token: tokenForFile(file),&lt;br /&gt;
                                    kind: &#039;flagurl&#039;,&lt;br /&gt;
                                    contentType: &#039;text/plain; charset=UTF-8&#039;&lt;br /&gt;
                                });&lt;br /&gt;
                            }&lt;br /&gt;
                        });&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; !seen[key] &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () { return flagUrlCache; });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeGenericFileTitle(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            var match;&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            for (i = 0; i &amp;lt; 4; i += 1) {&lt;br /&gt;
                try {&lt;br /&gt;
                    if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;).replace(/^\/+/, &#039;&#039;).replace(/^index\.php\/?/i, &#039;&#039;).replace(/^wiki\/?/i, &#039;&#039;).trim();&lt;br /&gt;
            match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/(.+)$/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^(?:File|파일|Image|이미지)\s*:/i, &#039;&#039;).replace(/^:+/, &#039;&#039;).trim();&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isFileRef(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            return /^(?:file|파일)\s*:/i.test(text) || /(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\//i.test(text);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileUrlKey(value) {&lt;br /&gt;
            return normalizeGenericFileTitle(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function stableDirectImageUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var separator;&lt;br /&gt;
            var bust;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            if (/[?&amp;amp;]_entryAsset=/.test(text) || /[?&amp;amp;]_=/.test(text)) return text;&lt;br /&gt;
            bust = String(BUILD_ID || &#039;entry&#039;);&lt;br /&gt;
            separator = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + separator + &#039;_entryAsset=&#039; + encodeURIComponent(bust);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFileUrl(ref) {&lt;br /&gt;
            var original = String(ref || &#039;&#039;).trim();&lt;br /&gt;
            var file;&lt;br /&gt;
            var key;&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!original) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (!isFileRef(original)) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            file = normalizeGenericFileTitle(original);&lt;br /&gt;
            key = fileUrlKey(file);&lt;br /&gt;
            if (!file || !key) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            if (fileUrlCache[key]) return Promise.resolve(fileUrlCache[key]);&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;fileurl&#039;) : &#039;&#039;;&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + key);&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        fileUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return fileUrlCache[key];&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                        titles: &#039;File:&#039; + file&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                        var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                        var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                        var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                        var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                        fileUrlCache[key] = resolved;&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, resolved, { resourceKey: resourceKey, token: token, kind: &#039;fileurl&#039;, contentType: &#039;text/plain; charset=UTF-8&#039; });&lt;br /&gt;
                        return resolved;&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return stableDirectImageUrl(original);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
            return fetchApi({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;,&lt;br /&gt;
                redirects: &#039;1&#039;,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + file&lt;br /&gt;
            }).then(function (json) {&lt;br /&gt;
                var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                fileUrlCache[key] = resolved;&lt;br /&gt;
                return resolved;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                return stableDirectImageUrl(original);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImageUrl(url, options) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var sourcePromise;&lt;br /&gt;
            var persistent = !(options &amp;amp;&amp;amp; options.persistent === false);&lt;br /&gt;
            if (!key) return Promise.resolve(false);&lt;br /&gt;
            if (imageReadyCache[key]) return Promise.resolve(true);&lt;br /&gt;
            if (imagePromiseCache[key]) return imagePromiseCache[key];&lt;br /&gt;
&lt;br /&gt;
            cacheKey = persistent &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039; ? window.RevisionManifest.cacheKeyForUrl(key, &#039;image&#039;) : &#039;&#039;;&lt;br /&gt;
            sourcePromise = Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.fetchBlobUrl === &#039;function&#039;) {&lt;br /&gt;
                sourcePromise = window.EntryCache.fetchBlobUrl(key, cacheKey, {&lt;br /&gt;
                    resourceKey: &#039;&#039;,&lt;br /&gt;
                    token: &#039;&#039;,&lt;br /&gt;
                    kind: &#039;image&#039;&lt;br /&gt;
                }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            imagePromiseCache[key] = sourcePromise.then(function (cachedObjectUrl) {&lt;br /&gt;
                return new Promise(function (resolve) {&lt;br /&gt;
                    var img = new Image();&lt;br /&gt;
                    var settled = false;&lt;br /&gt;
                    var src = cachedObjectUrl || key;&lt;br /&gt;
                    function finish(ok) {&lt;br /&gt;
                        if (settled) return;&lt;br /&gt;
                        settled = true;&lt;br /&gt;
                        if (ok) {&lt;br /&gt;
                            imageReadyCache[key] = true;&lt;br /&gt;
                            imageReadyCache[src] = true;&lt;br /&gt;
                            imageObjectCache[key] = img;&lt;br /&gt;
                            imageObjectCache[src] = img;&lt;br /&gt;
                            imageDisplayUrlCache[key] = src;&lt;br /&gt;
                            imageDisplayUrlCache[src] = src;&lt;br /&gt;
                        }&lt;br /&gt;
                        resolve(!!ok);&lt;br /&gt;
                    }&lt;br /&gt;
                    img.onload = function () {&lt;br /&gt;
                        if (img.decode) {&lt;br /&gt;
                            img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        } else {&lt;br /&gt;
                            finish(true);&lt;br /&gt;
                        }&lt;br /&gt;
                    };&lt;br /&gt;
                    img.onerror = function () {&lt;br /&gt;
                        if (src !== key) {&lt;br /&gt;
                            src = key;&lt;br /&gt;
                            img.src = key;&lt;br /&gt;
                            return;&lt;br /&gt;
                        }&lt;br /&gt;
                        finish(false);&lt;br /&gt;
                    };&lt;br /&gt;
                    img.decoding = &#039;async&#039;;&lt;br /&gt;
                    img.loading = &#039;eager&#039;;&lt;br /&gt;
                    img.src = src;&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        if (img.decode) img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        else finish(true);&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
            return imagePromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImages(urls, options) {&lt;br /&gt;
            var list = unique((urls || []).filter(Boolean));&lt;br /&gt;
            var limit = options &amp;amp;&amp;amp; Number(options.limit);&lt;br /&gt;
            var concurrency = Math.max(1, Math.min(48, Number(options &amp;amp;&amp;amp; options.concurrency) || 24));&lt;br /&gt;
            var index = 0;&lt;br /&gt;
            var ok = 0;&lt;br /&gt;
            var fail = 0;&lt;br /&gt;
            if (Number.isFinite(limit) &amp;amp;&amp;amp; limit &amp;gt; 0) list = list.slice(0, limit);&lt;br /&gt;
            if (!list.length) return Promise.resolve({ total: 0, ok: 0, fail: 0 });&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                function pump() {&lt;br /&gt;
                    while (index &amp;lt; list.length &amp;amp;&amp;amp; concurrency &amp;gt; 0) {&lt;br /&gt;
                        (function (url) {&lt;br /&gt;
                            concurrency -= 1;&lt;br /&gt;
                            preloadImageUrl(url, options || {}).then(function (result) {&lt;br /&gt;
                                if (result) ok += 1;&lt;br /&gt;
                                else fail += 1;&lt;br /&gt;
                            }).catch(function () {&lt;br /&gt;
                                fail += 1;&lt;br /&gt;
                            }).then(function () {&lt;br /&gt;
                                concurrency += 1;&lt;br /&gt;
                                if (index &amp;gt;= list.length &amp;amp;&amp;amp; ok + fail &amp;gt;= list.length) resolve({ total: list.length, ok: ok, fail: fail });&lt;br /&gt;
                                else pump();&lt;br /&gt;
                            });&lt;br /&gt;
                        })(list[index++]);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                pump();&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isImageReady(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return !!(key &amp;amp;&amp;amp; imageReadyCache[key]);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageElement(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageObjectCache[key] || null) : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageDisplayUrl(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageDisplayUrlCache[key] || key) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeUrlKey(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var a;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                a = document.createElement(&#039;a&#039;);&lt;br /&gt;
                a.href = text;&lt;br /&gt;
                text = a.pathname + (a.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            try {&lt;br /&gt;
                text = decodeURI(text);&lt;br /&gt;
            } catch (err2) {}&lt;br /&gt;
            text = text.replace(/([?&amp;amp;])_=[^&amp;amp;]*/g, &#039;$1&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            text = text.replace(/_/g, &#039;_&#039;);&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchTextUrl(url, options) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceRef;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty text url&#039;));&lt;br /&gt;
            if (textCache[key]) return Promise.resolve(textCache[key].text);&lt;br /&gt;
            if (textPromiseCache[key]) return textPromiseCache[key];&lt;br /&gt;
            resourceRef = options &amp;amp;&amp;amp; options.resourceRef ? options.resourceRef : (options &amp;amp;&amp;amp; options.ref ? options.ref : &#039;&#039;);&lt;br /&gt;
            cacheKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(resourceRef, &#039;text&#039;) : &#039;&#039;;&lt;br /&gt;
            if (!cacheKey &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039;) cacheKey = window.RevisionManifest.cacheKeyForUrl(url, &#039;text&#039;);&lt;br /&gt;
            resourceKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(resourceRef) : (&#039;url:&#039; + key);&lt;br /&gt;
            token = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(resourceRef) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                textPromiseCache[key] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: cachedText, loadedAt: now(), persistent: true };&lt;br /&gt;
                        return cachedText;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token, kind: &#039;text&#039;, contentType: &#039;text/html; charset=UTF-8&#039; });&lt;br /&gt;
                        return text;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return textPromiseCache[key];&lt;br /&gt;
            }&lt;br /&gt;
            textPromiseCache[key] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                return text;&lt;br /&gt;
            });&lt;br /&gt;
            return textPromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getTextSync(url) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            return key &amp;amp;&amp;amp; textCache[key] ? textCache[key].text : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setTextUrl(url, text) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            textCache[key] = { key: key, url: url, text: String(text || &#039;&#039;), loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheInfo() {&lt;br /&gt;
            return {&lt;br /&gt;
                json: Object.keys(jsonCache).length,&lt;br /&gt;
                jsonKeys: Object.keys(jsonCache),&lt;br /&gt;
                flags: Object.keys(flagUrlCache).length,&lt;br /&gt;
                files: Object.keys(fileUrlCache).length,&lt;br /&gt;
                images: Object.keys(imageReadyCache).length,&lt;br /&gt;
                retainedImages: Object.keys(imageObjectCache).length,&lt;br /&gt;
                displayImages: Object.keys(imageDisplayUrlCache).length,&lt;br /&gt;
                text: Object.keys(textCache).length,&lt;br /&gt;
                textKeys: Object.keys(textCache)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            fetchJsonRef: fetchJsonRef,&lt;br /&gt;
            getJsonSync: getJsonSync,&lt;br /&gt;
            setJsonRef: setJsonRef,&lt;br /&gt;
            normalizeRefKey: normalizeRefKey,&lt;br /&gt;
            rawUrlForRef: rawUrlForRef,&lt;br /&gt;
            revisionUrlForRef: function (ref, ctype) {&lt;br /&gt;
                var url = rawUrlForRef(ref, ctype);&lt;br /&gt;
                if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                    url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
                }&lt;br /&gt;
                return url;&lt;br /&gt;
            },&lt;br /&gt;
            resolveFlagUrls: resolveFlagUrls,&lt;br /&gt;
            setFlagUrl: setFlagUrl,&lt;br /&gt;
            getFlagUrl: getFlagUrl,&lt;br /&gt;
            resolveFileUrl: resolveFileUrl,&lt;br /&gt;
            preloadImageUrl: preloadImageUrl,&lt;br /&gt;
            preloadImages: preloadImages,&lt;br /&gt;
            isImageReady: isImageReady,&lt;br /&gt;
            getImageElement: getImageElement,&lt;br /&gt;
            getImageDisplayUrl: getImageDisplayUrl,&lt;br /&gt;
            fetchTextUrl: fetchTextUrl,&lt;br /&gt;
            getTextSync: getTextSync,&lt;br /&gt;
            setTextUrl: setTextUrl,&lt;br /&gt;
            stableDirectImageUrl: stableDirectImageUrl,&lt;br /&gt;
            cacheInfo: cacheInfo&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryStore = window.EntryStore || createEntryStore();&lt;br /&gt;
&lt;br /&gt;
    function updateBootProgress(done, total, label) {&lt;br /&gt;
        var pct = total ? Math.max(0, Math.min(100, Math.round((done / total) * 100))) : 0;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootDetailNode &amp;amp;&amp;amp; label) bootDetailNode.textContent = label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function adoptBootScreen(node) {&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        bootNode = node;&lt;br /&gt;
        bootStatusNode = bootNode.querySelector(&#039;.boot-gate-status&#039;);&lt;br /&gt;
        bootProgressNode = bootNode.querySelector(&#039;.boot-gate-progress&#039;);&lt;br /&gt;
        bootDetailNode = bootNode.querySelector(&#039;.boot-gate-detail&#039;);&lt;br /&gt;
        bootFillNode = bootNode.querySelector(&#039;.boot-gate-meter-fill&#039;);&lt;br /&gt;
        bootNode.classList.add(&#039;is-active&#039;);&lt;br /&gt;
        bootNode.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBootScreen(options) {&lt;br /&gt;
        var panel;&lt;br /&gt;
        var header;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var existing;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
        var previewMode;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        previewMode = !!options.preview;&lt;br /&gt;
        if (bootNode &amp;amp;&amp;amp; bootNode.parentNode) return bootNode;&lt;br /&gt;
        if (!document.body) return null;&lt;br /&gt;
&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !previewMode) return null;&lt;br /&gt;
        if (!previewMode) activateBootSurface();&lt;br /&gt;
&lt;br /&gt;
        existing = document.getElementById(&#039;boot-gate-screen&#039;) || (!previewMode &amp;amp;&amp;amp; window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.ensure ? window.__BootGatePrelude.ensure() : null);&lt;br /&gt;
        if (existing) return adoptBootScreen(existing);&lt;br /&gt;
&lt;br /&gt;
        bootNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootNode.id = &#039;boot-gate-screen&#039;;&lt;br /&gt;
        bootNode.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        bootNode.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        bootNode.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        header = document.createElement(&#039;div&#039;);&lt;br /&gt;
        header.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        header.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootStatusNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootStatusNode.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        bootStatusNode.textContent = &#039;Preparing entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        bootFillNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootFillNode.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(bootFillNode);&lt;br /&gt;
&lt;br /&gt;
        bootProgressNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootProgressNode.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        bootProgressNode.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootDetailNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootDetailNode.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        bootDetailNode.textContent = &#039;loading manifest&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { hideBootScreen({ force: true }); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(header);&lt;br /&gt;
        panel.appendChild(bootStatusNode);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(bootProgressNode);&lt;br /&gt;
        panel.appendChild(bootDetailNode);&lt;br /&gt;
        bootNode.appendChild(decoLayer);&lt;br /&gt;
        bootNode.appendChild(panel);&lt;br /&gt;
        bootNode.appendChild(close);&lt;br /&gt;
        document.body.appendChild(bootNode);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildLoginUrl() {&lt;br /&gt;
        var pageName = String(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        var params = {};&lt;br /&gt;
&lt;br /&gt;
        if (pageName &amp;amp;&amp;amp; !isAuthenticationPage() &amp;amp;&amp;amp; !isCreateAccountPage()) params.returnto = pageName;&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                return mw.util.getUrl(&#039;Special:UserLogin&#039;, params);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return &#039;/index.php?title=Special%3AUserLogin&#039; + (params.returnto ? &#039;&amp;amp;returnto=&#039; + encodeURIComponent(params.returnto) : &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureLoginGateAction(panel) {&lt;br /&gt;
        var copy;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (loginGateActionNode &amp;amp;&amp;amp; loginGateActionNode.parentNode) return loginGateActionNode;&lt;br /&gt;
        loginGateActionNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        loginGateActionNode.className = &#039;boot-gate-login&#039;;&lt;br /&gt;
&lt;br /&gt;
        copy = document.createElement(&#039;div&#039;);&lt;br /&gt;
        copy.className = &#039;boot-gate-login-copy&#039;;&lt;br /&gt;
        copy.textContent = &#039;SIGN IN TO ENTER THE WIKI.&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = document.createElement(&#039;a&#039;);&lt;br /&gt;
        action.className = &#039;boot-gate-login-action&#039;;&lt;br /&gt;
        action.href = buildLoginUrl();&lt;br /&gt;
        action.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        action.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
&lt;br /&gt;
        loginGateActionNode.appendChild(copy);&lt;br /&gt;
        loginGateActionNode.appendChild(action);&lt;br /&gt;
        panel.appendChild(loginGateActionNode);&lt;br /&gt;
        return loginGateActionNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showLoginGate() {&lt;br /&gt;
        var node = ensureBootScreen();&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (!node || !requiresLoginGate()) return false;&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        loginGateLocked = true;&lt;br /&gt;
        node.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.add(&#039;is-active&#039;, &#039;is-login-required&#039;);&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;dialog&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-modal&#039;, &#039;true&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;off&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = node.querySelector(&#039;.boot-gate-panel&#039;);&lt;br /&gt;
        title = node.querySelector(&#039;.boot-gate-title&#039;);&lt;br /&gt;
        if (title) title.textContent = &#039;ACCOUNT AUTHENTICATION&#039;;&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = &#039;LOGIN REQUIRED&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = &#039;100%&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = panel ? ensureLoginGateAction(panel).querySelector(&#039;.boot-gate-login-action&#039;) : null;&lt;br /&gt;
        if (action) {&lt;br /&gt;
            action.href = buildLoginUrl();&lt;br /&gt;
            window.requestAnimationFrame(function () {&lt;br /&gt;
                try { action.focus({ preventScroll: true }); }&lt;br /&gt;
                catch (err) { try { action.focus(); } catch (ignore) {} }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hideBootScreen(options) {&lt;br /&gt;
        var node = bootNode || document.getElementById(&#039;boot-gate-screen&#039;);&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        if (loginGateLocked &amp;amp;&amp;amp; requiresLoginGate() &amp;amp;&amp;amp; !options.force) return false;&lt;br /&gt;
        if (!node) {&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            if (bootNode === node) bootNode = null;&lt;br /&gt;
            loginGateActionNode = null;&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 240);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromNationPayload(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            if (typeof value === &#039;string&#039;) {&lt;br /&gt;
                files.push(value);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof value === &#039;object&#039;) files.push(value.file || value.flag_file || value.flag || value.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function scanItem(item) {&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            if (Array.isArray(item.flags)) item.flags.forEach(add);&lt;br /&gt;
            add(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        (payload &amp;amp;&amp;amp; payload.continents || []).forEach(function (continent) {&lt;br /&gt;
            (continent.regions || []).forEach(function (region) {&lt;br /&gt;
                (region.items || []).forEach(scanItem);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromLinkMap(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        var source = payload &amp;amp;&amp;amp; payload.items ? payload.items : {};&lt;br /&gt;
        Object.keys(source || {}).forEach(function (key) {&lt;br /&gt;
            var item = source[key];&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            files.push(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prewarmImages(urls, limit) {&lt;br /&gt;
        return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
            limit: Number(limit) &amp;gt; 0 ? Number(limit) : 0,&lt;br /&gt;
            concurrency: 16,&lt;br /&gt;
            persistent: false&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationRuntime() {&lt;br /&gt;
        return window.Decorations || window.CLBI_DECORATIONS || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationRuntime() {&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var tries = 0;&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var runtime = getDecorationRuntime();&lt;br /&gt;
                if (runtime) return resolve(runtime);&lt;br /&gt;
                tries += 1;&lt;br /&gt;
                if (tries &amp;gt; 40) return resolve(null);&lt;br /&gt;
                window.setTimeout(tick, 25);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesDecorationEntry(entry, filter) {&lt;br /&gt;
        var era = String(filter &amp;amp;&amp;amp; filter.era || &#039;&#039;).trim();&lt;br /&gt;
        var page = String(filter &amp;amp;&amp;amp; filter.page || &#039;&#039;).trim();&lt;br /&gt;
        var entryPage = String(entry &amp;amp;&amp;amp; entry.page || &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039;) return false;&lt;br /&gt;
        if (page &amp;amp;&amp;amp; entryPage &amp;amp;&amp;amp; entryPage !== page) return false;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() !== era) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDecorationSet(task, level) {&lt;br /&gt;
        var ref = task.ref || &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
        return window.EntryStore.fetchJsonRef(ref, { noStore: !!task.noStore }).then(function (registry) {&lt;br /&gt;
            var list = registry &amp;amp;&amp;amp; Array.isArray(registry.decorations) ? registry.decorations : [];&lt;br /&gt;
            var pixelRefs = [];&lt;br /&gt;
            list.forEach(function (entry) {&lt;br /&gt;
                var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).toLowerCase();&lt;br /&gt;
                var asset = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
                if (!asset) return;&lt;br /&gt;
                if (type !== &#039;pixel-json&#039; &amp;amp;&amp;amp; !/\.json(?:[?#].*)?$/i.test(asset)) return;&lt;br /&gt;
                if (!matchesDecorationEntry(entry, task)) return;&lt;br /&gt;
                pixelRefs.push(asset);&lt;br /&gt;
            });&lt;br /&gt;
            if (level !== &#039;full&#039; || task.preparePixels === false || !pixelRefs.length) return registry;&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (!runtime || typeof runtime.preparePixelCanvas !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                        return window.EntryStore.fetchJsonRef(pixelRef).catch(function () { return null; });&lt;br /&gt;
                    })).then(function () { return registry; });&lt;br /&gt;
                }&lt;br /&gt;
                return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(pixelRef).catch(function () { return null; });&lt;br /&gt;
                })).then(function () { return registry; });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareNationsEra(task, level) {&lt;br /&gt;
        var era = String(task.era || &#039;1950&#039;);&lt;br /&gt;
        var listRef = task.listRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_List.json&#039;);&lt;br /&gt;
        var linkRef = task.linkMapRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_Link_Map.json&#039;);&lt;br /&gt;
        var jsonPhaseId = BootPerf.start(&#039;nations-era json fetch&#039;, { era: era, level: level, listRef: listRef, linkRef: linkRef });&lt;br /&gt;
        var listPromise = window.EntryStore.fetchJsonRef(listRef).catch(function () { return null; });&lt;br /&gt;
        var linkPromise = window.EntryStore.fetchJsonRef(linkRef).catch(function () { return null; });&lt;br /&gt;
&lt;br /&gt;
        return Promise.all([listPromise, linkPromise]).then(function (results) {&lt;br /&gt;
            BootPerf.end(jsonPhaseId, { ok: true });&lt;br /&gt;
            var files;&lt;br /&gt;
            var flagUrls;&lt;br /&gt;
            if (level !== &#039;full&#039; &amp;amp;&amp;amp; level !== &#039;warm&#039;) return results;&lt;br /&gt;
            files = unique(collectFlagsFromNationPayload(results[0]).concat(collectFlagsFromLinkMap(results[1])));&lt;br /&gt;
            return BootPerf.measure(&#039;nations-era flag url resolve&#039;, { era: era, count: files.length }, function () {&lt;br /&gt;
                return window.EntryStore.resolveFlagUrls(files);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                if (level === &#039;warm&#039;) {&lt;br /&gt;
                    /*&lt;br /&gt;
                     * Warm tab contract — 20260708.&lt;br /&gt;
                     *&lt;br /&gt;
                     * A previous tab already stored the same-revision flag URL and image&lt;br /&gt;
                     * blobs.  Re-decoding every flag here makes the second tab feel as slow&lt;br /&gt;
                     * as the first one.  In warm mode we only hydrate the flag URL table from&lt;br /&gt;
                     * EntryCache so NationsPanel can render stable URLs immediately; image&lt;br /&gt;
                     * decode remains demand-driven by the live panel instead of blocking the&lt;br /&gt;
                     * boot screen.&lt;br /&gt;
                     */&lt;br /&gt;
                    return results;&lt;br /&gt;
                }&lt;br /&gt;
                flagUrls = files.map(function (file) { return window.EntryStore.getFlagUrl(file); }).filter(Boolean);&lt;br /&gt;
                /*&lt;br /&gt;
                 * 20260710: Flag URLs are part of the entry contract, but decoding every&lt;br /&gt;
                 * flag image is not.  The 1950 list has more than 150 flags, and waiting&lt;br /&gt;
                 * for all image decodes kept the public boot gate open for several seconds.&lt;br /&gt;
                 * Keep revision-aware URLs ready for the panel, then let the browser load&lt;br /&gt;
                 * the actual images from the live DOM.  A low-priority background warm-up&lt;br /&gt;
                 * may run after the boot gate has opened, so it never competes with the&lt;br /&gt;
                 * current entry surface or the globe ready contract.&lt;br /&gt;
                 */&lt;br /&gt;
                BootPerf.mark(&#039;nations-era flag image prewarm skipped&#039;, {&lt;br /&gt;
                    era: era,&lt;br /&gt;
                    count: flagUrls.length,&lt;br /&gt;
                    limit: task.flagLimit == null ? 0 : Number(task.flagLimit),&lt;br /&gt;
                    reason: &#039;deferred-after-boot-gate&#039;&lt;br /&gt;
                });&lt;br /&gt;
                if (flagUrls.length &amp;amp;&amp;amp; task.deferFlagImages !== false) {&lt;br /&gt;
                    deferredEntryWarmups.push(function () {&lt;br /&gt;
                        return BootPerf.measure(&#039;deferred nations-era flag image prewarm&#039;, {&lt;br /&gt;
                            era: era,&lt;br /&gt;
                            count: flagUrls.length,&lt;br /&gt;
                            limit: task.flagLimit == null ? 0 : Number(task.flagLimit)&lt;br /&gt;
                        }, function () {&lt;br /&gt;
                            return prewarmImages(flagUrls, task.flagLimit == null ? 0 : Number(task.flagLimit));&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                return results;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareGlobeSharedAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;globe-shared url resolve&#039;, { count: refs.length, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            /*&lt;br /&gt;
             * Globe shared assets are heavy 6K texture sources.  The live&lt;br /&gt;
             * NationsGlobe ready contract already waits for the actual Three.js&lt;br /&gt;
             * texture path, so decoding the same images here only duplicates work&lt;br /&gt;
             * and holds the boot screen.  Keep revision-aware URL resolution in the&lt;br /&gt;
             * entry ledger, but make the image warm-up non-blocking.&lt;br /&gt;
             */&lt;br /&gt;
            BootPerf.mark(&#039;globe-shared image prewarm skipped&#039;, {&lt;br /&gt;
                count: urls.length,&lt;br /&gt;
                level: level,&lt;br /&gt;
                reason: &#039;deferred-to-nations-globe-ready-contract&#039;&lt;br /&gt;
            });&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (!window.EntryStore || typeof window.EntryStore.preloadImages !== &#039;function&#039;) return;&lt;br /&gt;
                window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 4)),&lt;br /&gt;
                    persistent: false&lt;br /&gt;
                }).catch(function () {});&lt;br /&gt;
            }, 0);&lt;br /&gt;
            return urls;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareImageAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        var full = level === &#039;full&#039;;&lt;br /&gt;
        var exposeAs = String(task &amp;amp;&amp;amp; task.exposeAs || &#039;&#039;).trim();&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;image-assets url resolve&#039;, { count: refs.length, exposeAs: exposeAs, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            if (exposeAs === &#039;nationsGlobeLoadingGif&#039;) {&lt;br /&gt;
                window.NationsGlobeLoadingGifRef = refs[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifUrl = urls[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifFile = &#039;Gfx-vhs-glitch-001.gif&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (!full) return urls;&lt;br /&gt;
            return BootPerf.measure(&#039;image-assets image prewarm&#039;, { count: urls.length, exposeAs: exposeAs, limit: task.imageLimit == null ? 0 : Number(task.imageLimit), concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2)) }, function () {&lt;br /&gt;
                return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2))&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () { return urls; });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareHtmlEntry(task) {&lt;br /&gt;
        var url = String(task &amp;amp;&amp;amp; (task.url || task.ref) || &#039;&#039;).trim();&lt;br /&gt;
        if (!url) return Promise.resolve(null);&lt;br /&gt;
        return window.EntryStore.fetchTextUrl(url, { noStore: !!task.noStore, resourceRef: task.resourceRef || task.ref || task.title || &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareTask(task, defaultLevel) {&lt;br /&gt;
        var level = String(task &amp;amp;&amp;amp; (task.level || defaultLevel) || &#039;half&#039;).toLowerCase();&lt;br /&gt;
        var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
        if (!task || typeof task !== &#039;object&#039;) return Promise.resolve(null);&lt;br /&gt;
        if (type === &#039;html&#039;) return prepareHtmlEntry(task);&lt;br /&gt;
        if (type === &#039;json&#039;) return window.EntryStore.fetchJsonRef(task.ref, { noStore: !!task.noStore });&lt;br /&gt;
        if (type === &#039;pixel-json&#039;) {&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (level === &#039;full&#039; &amp;amp;&amp;amp; runtime &amp;amp;&amp;amp; typeof runtime.preparePixelCanvas === &#039;function&#039;) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(task.ref || task.asset);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(task.ref || task.asset);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        if (type === &#039;decorations&#039;) return prepareDecorationSet(task, level);&lt;br /&gt;
        if (type === &#039;nations-era&#039;) return prepareNationsEra(task, level);&lt;br /&gt;
        if (type === &#039;globe-shared-assets&#039;) return prepareGlobeSharedAssets(task, level);&lt;br /&gt;
        if (type === &#039;image-assets&#039;) return prepareImageAssets(task, level);&lt;br /&gt;
        return Promise.resolve(null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifest() {&lt;br /&gt;
        return BootPerf.measure(&#039;revision manifest load&#039;, {}, function () {&lt;br /&gt;
            return (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.load === &#039;function&#039; ? window.RevisionManifest.load() : Promise.resolve(null));&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;entry manifest json fetch&#039;, { ref: MANIFEST_TITLE }, function () {&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(MANIFEST_TITLE, { noStore: false });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (manifest) {&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039; || !manifest.version) {&lt;br /&gt;
                BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: &#039;invalid manifest&#039; });&lt;br /&gt;
                return defaultManifest;&lt;br /&gt;
            }&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest ready&#039;, { version: manifest.version });&lt;br /&gt;
            return manifest;&lt;br /&gt;
        }).catch(function (err) {&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: err &amp;amp;&amp;amp; (err.message || String(err)) || &#039;load failed&#039; });&lt;br /&gt;
            return defaultManifest;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function flattenInitialTasks(manifest) {&lt;br /&gt;
        var initial = manifest &amp;amp;&amp;amp; manifest.initial ? manifest.initial : {};&lt;br /&gt;
        var full = Array.isArray(initial.full) ? initial.full : [];&lt;br /&gt;
        var half = Array.isArray(initial.half) ? initial.half : [];&lt;br /&gt;
        var tasks = [];&lt;br /&gt;
        full.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;full&#039;;&lt;br /&gt;
            task.blocking = true;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        half.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;half&#039;;&lt;br /&gt;
            task.blocking = false;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        return tasks;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function flattenWarmInitialTasks(tasks) {&lt;br /&gt;
        var warmed = [];&lt;br /&gt;
        (tasks || []).forEach(function (task) {&lt;br /&gt;
            var copy;&lt;br /&gt;
            var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
            if (!task || task.blocking === false) return;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Warm boot fast path — 20260708.&lt;br /&gt;
             *&lt;br /&gt;
             * Cold boot intentionally performs the expensive work: resolving every flag,&lt;br /&gt;
             * downloading/decoding images, preparing decoration canvases, and warming shared&lt;br /&gt;
             * globe textures.  Once a tab has certified the pack against the current revision&lt;br /&gt;
             * manifest, later tabs must not replay that full workload.  They only hydrate the&lt;br /&gt;
             * small tables needed by the live page and let already-cached image/blob data be&lt;br /&gt;
             * consumed on demand.  This is the missing layer that made second tabs feel almost&lt;br /&gt;
             * as slow as first tabs even though the string checks were true.&lt;br /&gt;
             */&lt;br /&gt;
            if (type === &#039;globe-shared-assets&#039;) return;&lt;br /&gt;
            if (type === &#039;image-assets&#039;) return;&lt;br /&gt;
            copy = Object.assign({}, task);&lt;br /&gt;
            if (type === &#039;nations-era&#039;) copy.level = &#039;warm&#039;;&lt;br /&gt;
            else if (type === &#039;decorations&#039;) {&lt;br /&gt;
                copy.level = &#039;half&#039;;&lt;br /&gt;
                copy.preparePixels = false;&lt;br /&gt;
            } else if (type === &#039;pixel-json&#039;) copy.level = &#039;half&#039;;&lt;br /&gt;
            warmed.push(copy);&lt;br /&gt;
        });&lt;br /&gt;
        return warmed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackKey(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        return &#039;initial-entry-full:&#039; + version;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackToken(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        var revToken = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(version) : version;&lt;br /&gt;
        return revToken;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isInitialPackWarm(manifest) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        return !!(window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.packReady === &#039;function&#039; &amp;amp;&amp;amp; window.EntryCache.packReady(key, token));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function markInitialPackWarm(manifest, meta) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        if (window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.setPackReady === &#039;function&#039;) {&lt;br /&gt;
            window.EntryCache.setPackReady(key, token, Object.assign({ manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || BUILD_ID }, meta || {}));&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function waitMs(ms) {&lt;br /&gt;
        return new Promise(function (resolve) { window.setTimeout(resolve, Math.max(0, ms || 0)); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForBody() {&lt;br /&gt;
        if (document.body) return Promise.resolve(document.body);&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                if (document.body) return resolve(document.body);&lt;br /&gt;
                window.setTimeout(tick, 10);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDomReady() {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) return Promise.resolve();&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, resolve, { once: true });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForAnimationFrames(count) {&lt;br /&gt;
        count = Math.max(1, Math.round(count || 1));&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function next(left) {&lt;br /&gt;
                if (left &amp;lt;= 0) return resolve();&lt;br /&gt;
                window.requestAnimationFrame(function () { next(left - 1); });&lt;br /&gt;
            }&lt;br /&gt;
            next(count);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitUntil(predicate, options) {&lt;br /&gt;
        var started = now();&lt;br /&gt;
        var timeout = options &amp;amp;&amp;amp; options.timeoutMs ? options.timeoutMs : 8000;&lt;br /&gt;
        var interval = options &amp;amp;&amp;amp; options.intervalMs ? options.intervalMs : 60;&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var result = false;&lt;br /&gt;
                try { result = predicate(); } catch (err) { result = false; }&lt;br /&gt;
                if (result) return resolve({ ok: true, value: result });&lt;br /&gt;
                if (now() - started &amp;gt;= timeout) return resolve({ ok: false, timeout: true });&lt;br /&gt;
                window.setTimeout(tick, interval);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForTrackedScripts() {&lt;br /&gt;
        var list = (window.EntryScriptLoads || []).slice();&lt;br /&gt;
        if (!list.length) return Promise.resolve([]);&lt;br /&gt;
        return Promise.all(list.map(function (promise) {&lt;br /&gt;
            return Promise.resolve(promise).catch(function (err) { return { ok: false, error: err }; });&lt;br /&gt;
        }));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDocumentSurface() {&lt;br /&gt;
        return waitForDomReady().then(function () {&lt;br /&gt;
            return waitForBody();&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                return document.querySelector(&#039;.content-wrapper&#039;) &amp;amp;&amp;amp; document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
            }, { timeoutMs: 8000, intervalMs: 50 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isNationsPageSurface() {&lt;br /&gt;
        var page = String(mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
        return !!document.querySelector(&#039;.clbi-nations-panel-stack&#039;) || /(?:^|[_ ])시대(?:$|[_ ])/.test(page) || /(?:^|[_ ])Era(?:$|[_ ])/i.test(page);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getInitialNationsEra(manifest) {&lt;br /&gt;
        var tasks = flattenInitialTasks(manifest);&lt;br /&gt;
        var i;&lt;br /&gt;
        for (i = 0; i &amp;lt; tasks.length; i += 1) {&lt;br /&gt;
            if (tasks[i] &amp;amp;&amp;amp; tasks[i].type === &#039;nations-era&#039; &amp;amp;&amp;amp; String(tasks[i].level || &#039;&#039;).toLowerCase() === &#039;full&#039;) {&lt;br /&gt;
                return String(tasks[i].era || &#039;1950&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;1950&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsPanelReady(era) {&lt;br /&gt;
        return BootPerf.measure(&#039;wait nations panel api&#039;, { era: era }, function () {&lt;br /&gt;
            return waitUntil(function () { return window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.whenEraReady === &#039;function&#039;; }, {&lt;br /&gt;
                timeoutMs: 8000,&lt;br /&gt;
                intervalMs: 50&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (result) {&lt;br /&gt;
            if (!result.ok || !window.NationsPanel || typeof window.NationsPanel.whenEraReady !== &#039;function&#039;) return null;&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel era ready&#039;, { era: era }, function () {&lt;br /&gt;
                return window.NationsPanel.whenEraReady(era, { timeoutMs: 15000 }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel dom ready&#039;, { era: era }, function () {&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var panel = document.querySelector(&#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + era + &#039;&amp;quot;] .clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;) ||&lt;br /&gt;
                        document.querySelector(&#039;.clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
                    return panel &amp;amp;&amp;amp; panel.classList.contains(&#039;clbi-nations-list-json-ready&#039;) &amp;amp;&amp;amp; panel.getAttribute(&#039;data-nation-list-year-loaded&#039;) === String(era);&lt;br /&gt;
                }, { timeoutMs: 15000, intervalMs: 80 });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsGlobeReady(options) {&lt;br /&gt;
        var globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
        var soft;&lt;br /&gt;
        var timeout;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        soft = !!options.soft;&lt;br /&gt;
        timeout = Number(options.timeoutMs || (soft ? 450 : 30000));&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            BootPerf.mark(&#039;wait nations globe skipped&#039;, { reason: &#039;no globe node&#039;, soft: soft });&lt;br /&gt;
            return Promise.resolve(null);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return BootPerf.measure(soft ? &#039;wait nations globe warm attach&#039; : &#039;wait nations globe ready contract&#039;, { soft: soft, timeoutMs: timeout }, function () {&lt;br /&gt;
            if (soft) {&lt;br /&gt;
                /*&lt;br /&gt;
                Warm-tab policy:&lt;br /&gt;
                Raw globe images are already revision-checked and blob-cached by EntryCache,&lt;br /&gt;
                but WebGL scene creation, GPU upload, topojson parsing, and per-tab Three.js&lt;br /&gt;
                objects cannot be shared across browser tabs.  Waiting for the full is-ready&lt;br /&gt;
                contract here makes a warm tab slower than the first tab.  In warm mode the&lt;br /&gt;
                initial gate only waits until the globe consumer has been attached, then the&lt;br /&gt;
                globe finishes its own per-tab hydrate without holding the whole page hostage.&lt;br /&gt;
                */&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                    if (!node) return false;&lt;br /&gt;
                    return node.getAttribute(&#039;data-nations-globe-ready&#039;) === &#039;1&#039; ||&lt;br /&gt;
                        !!node.CLBI_NationsGlobeInstance ||&lt;br /&gt;
                        !!node.querySelector(&#039;.clbi-nations-globe-stage&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;is-ready&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;has-error&#039;);&lt;br /&gt;
                }, { timeoutMs: timeout, intervalMs: 40 });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                return node &amp;amp;&amp;amp; (node.classList.contains(&#039;is-ready&#039;) || node.classList.contains(&#039;has-error&#039;));&lt;br /&gt;
            }, { timeoutMs: timeout, intervalMs: 100 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationsReady(options) {&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
            if (!runtime) return null;&lt;br /&gt;
            if (options.warm) {&lt;br /&gt;
                /*&lt;br /&gt;
                 * Warm tab contract — 20260708.&lt;br /&gt;
                 *&lt;br /&gt;
                 * The decoration registry and pixel canvases were prepared by the cold tab.&lt;br /&gt;
                 * Calling reload() here re-reads and re-syncs the decoration layer during the&lt;br /&gt;
                 * boot gate, which is visible as a pointless delay.  A warm tab only needs the&lt;br /&gt;
                 * current visibility pass so boot can release quickly and editing/preview tools&lt;br /&gt;
                 * remain responsive.&lt;br /&gt;
                 */&lt;br /&gt;
                try {&lt;br /&gt;
                    if (typeof runtime.updateVisibility === &#039;function&#039;) runtime.updateVisibility(document);&lt;br /&gt;
                    else if (typeof runtime.sync === &#039;function&#039;) runtime.sync();&lt;br /&gt;
                } catch (err) {}&lt;br /&gt;
                return waitForAnimationFrames(1);&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof runtime.reload !== &#039;function&#039;) return null;&lt;br /&gt;
            return runtime.reload().catch(function () { return null; }).then(function () {&lt;br /&gt;
                return waitForAnimationFrames(2);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForCurrentEntrySurface(manifest, options) {&lt;br /&gt;
        var era = getInitialNationsEra(manifest);&lt;br /&gt;
        var warmPack;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        warmPack = !!options.warmPack;&lt;br /&gt;
&lt;br /&gt;
        if (!isNationsPageSurface()) {&lt;br /&gt;
            return waitForDecorationsReady({ warm: !!warmPack }).then(function () { return waitForAnimationFrames(1); });&lt;br /&gt;
        }&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = warmPack ? &#039;Hydrating current information surface from warm cache&#039; : &#039;Preparing current information surface&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for era &#039; + era + &#039; ready contract&#039;;&lt;br /&gt;
        return waitForNationsPanelReady(era)&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (warmPack) {&lt;br /&gt;
                    if (bootDetailNode) bootDetailNode.textContent = &#039;attaching globe warm consumer&#039;;&lt;br /&gt;
                    return waitForNationsGlobeReady({ soft: true, timeoutMs: 450 });&lt;br /&gt;
                }&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for globe ready contract&#039;;&lt;br /&gt;
                return waitForNationsGlobeReady();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for decoration surface&#039;;&lt;br /&gt;
                return waitForDecorationsReady({ warm: warmPack });&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () { return waitForAnimationFrames(warmPack ? 1 : 2); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runDeferredEntryWarmups() {&lt;br /&gt;
        var queue = deferredEntryWarmups.splice(0);&lt;br /&gt;
        if (!queue.length) return;&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            var chain = Promise.resolve();&lt;br /&gt;
            queue.forEach(function (job) {&lt;br /&gt;
                chain = chain.then(function () {&lt;br /&gt;
                    try { return job(); }&lt;br /&gt;
                    catch (err) { return null; }&lt;br /&gt;
                }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }, 1200);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runInitialLoad(options) {&lt;br /&gt;
        var done = 0;&lt;br /&gt;
        var manifestRef;&lt;br /&gt;
        var tasks;&lt;br /&gt;
        var bootOptions;&lt;br /&gt;
        var timedOut = false;&lt;br /&gt;
        var warmPack = false;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        bootStartTime = window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.startTime ? window.__BootGatePrelude.startTime : now();&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        ensureBootScreen();&lt;br /&gt;
        updateBootProgress(0, 1, &#039;loading entry manifest&#039;);&lt;br /&gt;
&lt;br /&gt;
        manifestRef = loadManifest().then(function (manifest) {&lt;br /&gt;
            var minDisplay;&lt;br /&gt;
            var totalSteps;&lt;br /&gt;
            var maxBlockingMs;&lt;br /&gt;
            var timeoutHandle;&lt;br /&gt;
            bootOptions = manifest.boot || {};&lt;br /&gt;
            warmPack = isInitialPackWarm(manifest);&lt;br /&gt;
            minDisplay = options.minDisplayMs || (warmPack ? (bootOptions.cachedMinDisplayMs || 220) : (bootOptions.minDisplayMs || 950));&lt;br /&gt;
            tasks = flattenInitialTasks(manifest);&lt;br /&gt;
            if (!tasks.length) tasks = flattenInitialTasks(defaultManifest);&lt;br /&gt;
            if (warmPack) tasks = flattenWarmInitialTasks(tasks);&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            Full/half contract:&lt;br /&gt;
            These manifest tasks only prepare data assets.  The gate is not allowed to open&lt;br /&gt;
            until the current page surface has consumed that data and reported a real ready&lt;br /&gt;
            state below.  Do not reintroduce localStorage-only skip logic here; a cached&lt;br /&gt;
            version number is not the same as current-tab readiness.&lt;br /&gt;
            */&lt;br /&gt;
            totalSteps = tasks.length + 4;&lt;br /&gt;
            updateBootProgress(0, totalSteps, warmPack ? &#039;hydrating warm entry cache&#039; : &#039;starting entry packs&#039;);&lt;br /&gt;
&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                maxBlockingMs = Number(options.maxBlockingMs || bootOptions.maxBlockingMs || 30000);&lt;br /&gt;
                timeoutHandle = window.setTimeout(function () {&lt;br /&gt;
                    timedOut = true;&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }, maxBlockingMs);&lt;br /&gt;
&lt;br /&gt;
                Promise.all(tasks.map(function (task) {&lt;br /&gt;
                    var label = task.label || task.id || task.type || &#039;entry task&#039;;&lt;br /&gt;
                    return BootPerf.measure(&#039;boot task: &#039; + label, { id: task.id || &#039;&#039;, type: task.type || &#039;&#039;, level: task.level || task.level === 0 ? task.level : &#039;&#039; }, function () {&lt;br /&gt;
                        return prepareTask(task, task.level);&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }).then(function () {&lt;br /&gt;
                        done += 1;&lt;br /&gt;
                        updateBootProgress(done, totalSteps, label);&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;loading subsystem scripts&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait tracked subsystem scripts&#039;, { count: (window.EntryScriptLoads || []).length }, function () { return waitForTrackedScripts(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for document shell&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait document shell&#039;, {}, function () { return waitForDocumentSurface(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for current entry surface&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait current entry surface&#039;, { warmPack: warmPack }, function () { return waitForCurrentEntrySurface(manifest, { warmPack: warmPack }); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(totalSteps, totalSteps, warmPack ? &#039;warm entry surface complete&#039; : &#039;entry surface complete&#039;);&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                var elapsed = now() - bootStartTime;&lt;br /&gt;
                var wait = Math.max(0, minDisplay - elapsed);&lt;br /&gt;
                return waitMs(wait);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                localStorage.setItem(READY_KEY, String(manifest.version));&lt;br /&gt;
                if (!timedOut) markInitialPackWarm(manifest, { warmSource: warmPack ? &#039;cache-hit&#039; : &#039;cold-build&#039; });&lt;br /&gt;
                if (bootStatusNode) bootStatusNode.textContent = timedOut ? &#039;Entry surface ready with deferred items&#039; : (warmPack ? &#039;Entry surface ready from warm cache&#039; : &#039;Entry surface ready&#039;);&lt;br /&gt;
                updateBootProgress(tasks.length + 4, tasks.length + 4, timedOut ? &#039;timeout: continuing deferred loading&#039; : (warmPack ? &#039;warm cache complete&#039; : &#039;complete&#039;));&lt;br /&gt;
                BootPerf.mark(&#039;boot gate complete&#039;, { timedOut: timedOut, warmPack: warmPack, manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || &#039;&#039; });&lt;br /&gt;
                BootPerf.print();&lt;br /&gt;
                runDeferredEntryWarmups();&lt;br /&gt;
                window.setTimeout(function () {&lt;br /&gt;
                    if (requiresLoginGate()) showLoginGate();&lt;br /&gt;
                    else hideBootScreen();&lt;br /&gt;
                }, 120);&lt;br /&gt;
                return manifest;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return manifestRef;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startBoot(options) {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return Promise.resolve({ skipped: true, reason: &#039;developer-or-editing-page&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        if (hasBootParam(&#039;0&#039;) &amp;amp;&amp;amp; !requiresLoginGate() &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return Promise.resolve(null);&lt;br /&gt;
        if (bootStarted &amp;amp;&amp;amp; bootPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return bootPromise;&lt;br /&gt;
        bootStarted = true;&lt;br /&gt;
        bootPromise = runInitialLoad(options || {});&lt;br /&gt;
        return bootPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resetBoot() {&lt;br /&gt;
        localStorage.removeItem(READY_KEY);&lt;br /&gt;
        bootStarted = false;&lt;br /&gt;
        bootPromise = null;&lt;br /&gt;
        loginGateLocked = false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    BootGate intentionally has no SPA hold/release API.&lt;br /&gt;
    Initial boot is the only blocking phase; later SPA routes must consume prepared&lt;br /&gt;
    EntryStore artifacts without reopening the loading surface.&lt;br /&gt;
    */&lt;br /&gt;
&lt;br /&gt;
    function buildBootReport() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD_ID,&lt;br /&gt;
            state: {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                excludedPage: BOOT_EXCLUDED_PAGE,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;))&lt;br /&gt;
            },&lt;br /&gt;
            store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
            bootPerf: window.BootPerf &amp;amp;&amp;amp; typeof window.BootPerf.summary === &#039;function&#039; ? window.BootPerf.summary() : null,&lt;br /&gt;
            revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null,&lt;br /&gt;
            nations: window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.diagnostics === &#039;function&#039; ? window.NationsPanel.diagnostics() : null,&lt;br /&gt;
            decorations: window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.diagnostics === &#039;function&#039; ? window.Decorations.diagnostics() : null&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryLoader = window.EntryLoader || {&lt;br /&gt;
        loadManifest: loadManifest,&lt;br /&gt;
        prepareTask: prepareTask,&lt;br /&gt;
        prepareNationsEra: prepareNationsEra,&lt;br /&gt;
        prepareDecorationSet: prepareDecorationSet,&lt;br /&gt;
        prepareGlobeSharedAssets: prepareGlobeSharedAssets,&lt;br /&gt;
        prepareImageAssets: prepareImageAssets,&lt;br /&gt;
        prepareHtmlEntry: prepareHtmlEntry,&lt;br /&gt;
        runInitialLoad: runInitialLoad&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function showBootPreview(options) {&lt;br /&gt;
        var node;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        /*&lt;br /&gt;
         * Boot preview is a design/editing surface, not a real boot gate.&lt;br /&gt;
         * The earlier preview reused activateBootSurface(), which applied&lt;br /&gt;
         * html.boot-gate-active and hid the entire wiki shell, including&lt;br /&gt;
         * DevTools.  That made it impossible to edit loading-screen&lt;br /&gt;
         * decorations while previewing them.  Keep the real first-entry gate&lt;br /&gt;
         * full-screen, but make preview a small non-blocking surface below the&lt;br /&gt;
         * DevTools z-index so the owner can keep using the editor.&lt;br /&gt;
         */&lt;br /&gt;
        node = ensureBootScreen({ preview: true });&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        node.classList.add(&#039;is-preview&#039;);&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = options.status || &#039;Loading screen preview&#039;;&lt;br /&gt;
        updateBootProgress(Number(options.progress || 64), 100, options.detail || &#039;preview mode: no entry tasks are running&#039;);&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BootGate = window.BootGate || {&lt;br /&gt;
        version: BUILD_ID,&lt;br /&gt;
        start: startBoot,&lt;br /&gt;
        show: function (options) {&lt;br /&gt;
            options = options || {};&lt;br /&gt;
            options.force = true;&lt;br /&gt;
            resetBoot();&lt;br /&gt;
            return startBoot(options);&lt;br /&gt;
        },&lt;br /&gt;
        reset: resetBoot,&lt;br /&gt;
        hide: hideBootScreen,&lt;br /&gt;
        preview: showBootPreview,&lt;br /&gt;
        report: buildBootReport,&lt;br /&gt;
        state: function () {&lt;br /&gt;
            return {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                prelude: !!window.__BootGatePrelude,&lt;br /&gt;
                store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
                revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function prime() {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        waitForBody().then(function () {&lt;br /&gt;
            ensureBootScreen();&lt;br /&gt;
            return waitForAnimationFrames(1);&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            startBoot();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    waitForBody().then(function () {&lt;br /&gt;
        if (!BOOT_EXCLUDED_PAGE) ensureBootScreen();&lt;br /&gt;
        else hideBootScreen();&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(prime, 0);&lt;br /&gt;
})(window, document, window.mediaWiki || window.mw);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:DevTools.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsPanel.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsGlobe.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* CLBI safety guard: adaptive reset functions must exist before shell metric callbacks run. */&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.resetLeftRecentAdaptiveState = resetLeftRecentAdaptiveState;&lt;br /&gt;
window.resetLeftBillboardAdaptiveState = resetLeftBillboardAdaptiveState;&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:AnecdoteViewer.js&#039;);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SYSTEM_TITLE_NAMESPACES = {&lt;br /&gt;
        &#039;-1&#039;: true,&lt;br /&gt;
        &#039;4&#039;: true,&lt;br /&gt;
        &#039;5&#039;: true,&lt;br /&gt;
        &#039;6&#039;: true,&lt;br /&gt;
        &#039;7&#039;: true,&lt;br /&gt;
        &#039;8&#039;: true,&lt;br /&gt;
        &#039;9&#039;: true,&lt;br /&gt;
        &#039;10&#039;: true,&lt;br /&gt;
        &#039;11&#039;: true,&lt;br /&gt;
        &#039;12&#039;: true,&lt;br /&gt;
        &#039;13&#039;: true,&lt;br /&gt;
        &#039;14&#039;: true,&lt;br /&gt;
        &#039;15&#039;: true,&lt;br /&gt;
        &#039;828&#039;: true,&lt;br /&gt;
        &#039;829&#039;: true&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function normalizePageNameForShell(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readCurrentPageNameForShell() {&lt;br /&gt;
        var pageName = mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (pageName) {&lt;br /&gt;
            return normalizePageNameForShell(pageName);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return normalizePageNameForShell(window.location.pathname || &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnecdoteNamespaceForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var canonicalNamespace = String(mw.config.get(&#039;wgCanonicalNamespace&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 3000 ||&lt;br /&gt;
            canonicalNamespace === &#039;anecdote&#039; ||&lt;br /&gt;
            /^(anecdote|에넥도트):/i.test(pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBackendOrSystemPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var action = String(mw.config.get(&#039;wgAction&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var lowerPageName = pageName.toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === &#039;대문&#039;) {&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (SYSTEM_TITLE_NAMESPACES[String(namespaceNumber)]) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;json&#039; || contentModel === &#039;sanitized-css&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/\.(css|js|json)$/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/^(mediawiki|미디어위키|special|특수):/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMediaWikiSystemAssetPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 8 &amp;amp;&amp;amp;&lt;br /&gt;
            (/\.(css|js)$/i.test(pageName) || contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;sanitized-css&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var systemDocRawFetchToken = 0;&lt;br /&gt;
&lt;br /&gt;
    function cleanupLegacySystemDocCodeMutationsForShell() {&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codepane&#039;).forEach(function (pane) {&lt;br /&gt;
            var parent;&lt;br /&gt;
&lt;br /&gt;
            if (!pane || !pane.parentNode) return;&lt;br /&gt;
&lt;br /&gt;
            parent = pane.parentNode;&lt;br /&gt;
            while (pane.firstChild) {&lt;br /&gt;
                parent.insertBefore(pane.firstChild, pane);&lt;br /&gt;
            }&lt;br /&gt;
            parent.removeChild(pane);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codebox&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;style&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocOutputForShell() {&lt;br /&gt;
        return document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findSystemDocSourceNodeForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var children;&lt;br /&gt;
        var preferred;&lt;br /&gt;
&lt;br /&gt;
        if (!output) return null;&lt;br /&gt;
&lt;br /&gt;
        children = Array.prototype.slice.call(output.children || [])&lt;br /&gt;
            .filter(function (el) {&lt;br /&gt;
                return el &amp;amp;&amp;amp; el.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-doc-indicator-row&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-source-viewer&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    !el.classList.contains(&#039;catlinks&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    (el.textContent || &#039;&#039;).trim().length &amp;gt; 200;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
        preferred = children.filter(function (el) {&lt;br /&gt;
            return el.matches &amp;amp;&amp;amp; el.matches(&#039;.mw-highlight, .mw-code, pre&#039;);&lt;br /&gt;
        })[0];&lt;br /&gt;
&lt;br /&gt;
        return preferred || children.sort(function (a, b) {&lt;br /&gt;
            return (b.textContent || &#039;&#039;).trim().length - (a.textContent || &#039;&#039;).trim().length;&lt;br /&gt;
        })[0] || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocRawUrlForShell() {&lt;br /&gt;
        var title = mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell();&lt;br /&gt;
        var url;&lt;br /&gt;
&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            url = mw.util.getUrl(title, {&lt;br /&gt;
                action: &#039;raw&#039;,&lt;br /&gt;
                ctype: &#039;text/plain&#039;&lt;br /&gt;
            });&lt;br /&gt;
        } else {&lt;br /&gt;
            url = &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/plain&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, title);&lt;br /&gt;
        }&lt;br /&gt;
        return url;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (viewer &amp;amp;&amp;amp; viewer.parentNode) {&lt;br /&gt;
            viewer.parentNode.removeChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-original-source-hidden&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-source-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureSystemDocSourceViewerForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var source;&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var fallbackText;&lt;br /&gt;
&lt;br /&gt;
        if (!output || !isMediaWikiSystemAssetPageForShell()) return null;&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
&lt;br /&gt;
        source = findSystemDocSourceNodeForShell();&lt;br /&gt;
        if (!source) return null;&lt;br /&gt;
&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!viewer) {&lt;br /&gt;
            viewer = document.createElement(&#039;pre&#039;);&lt;br /&gt;
            viewer.id = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            viewer.className = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            output.appendChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fallbackText = source.textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!viewer.textContent &amp;amp;&amp;amp; fallbackText) {&lt;br /&gt;
            viewer.textContent = fallbackText;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        source.classList.add(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
        source.setAttribute(&#039;data-clbi-system-source-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        source.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
        return viewer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var token;&lt;br /&gt;
        var currentScrollTop;&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = String(mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell());&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        시스템 문서 뷰어가 이미 만들어져 있고 raw 원문도 로드된 상태라면&lt;br /&gt;
        다시 source 탐색/숨김/스타일 재적용을 하지 않는다.&lt;br /&gt;
        DevTools Elements 패널에서 body가 계속 파랗게 깜빡이던 원인은&lt;br /&gt;
        MutationObserver가 이 재적용을 반복해서 DOM attribute mutation을 만들었기 때문이다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            viewer &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-title&#039;) === pageName &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-loaded&#039;) === &#039;1&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        viewer = ensureSystemDocSourceViewerForShell();&lt;br /&gt;
        if (!viewer) return;&lt;br /&gt;
&lt;br /&gt;
        currentScrollTop = viewer.scrollTop || 0;&lt;br /&gt;
        viewer.setAttribute(&#039;data-clbi-raw-title&#039;, pageName);&lt;br /&gt;
        token = ++systemDocRawFetchToken;&lt;br /&gt;
&lt;br /&gt;
        fetch(getSystemDocRawUrlForShell(), { credentials: &#039;same-origin&#039; })&lt;br /&gt;
            .then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;raw fetch failed &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (token !== systemDocRawFetchToken) return;&lt;br /&gt;
&lt;br /&gt;
                currentScrollTop = viewer.scrollTop || currentScrollTop || 0;&lt;br /&gt;
&lt;br /&gt;
                if (text &amp;amp;&amp;amp; viewer.textContent !== text) {&lt;br /&gt;
                    viewer.textContent = text;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;1&#039;);&lt;br /&gt;
                viewer.scrollTop = currentScrollTop;&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;0&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocIndicatorForShell() {&lt;br /&gt;
        var existing = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            document.body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (existing &amp;amp;&amp;amp; existing.parentNode) {&lt;br /&gt;
            existing.parentNode.removeChild(existing);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        removeSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocIndicatorForShell() {&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var extMatch;&lt;br /&gt;
        var ext;&lt;br /&gt;
        var row;&lt;br /&gt;
        var box;&lt;br /&gt;
        var meta;&lt;br /&gt;
        var label;&lt;br /&gt;
        var type;&lt;br /&gt;
        var title;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var main;&lt;br /&gt;
&lt;br /&gt;
        if (!document.body || !isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = readCurrentPageNameForShell();&lt;br /&gt;
        extMatch = pageName.match(/\.(css|js)$/i);&lt;br /&gt;
        ext = extMatch ? extMatch[1].toUpperCase() : &#039;DOC&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.classList.add(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        row = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!row) {&lt;br /&gt;
            row = document.createElement(&#039;div&#039;);&lt;br /&gt;
            row.id = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
            row.className = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
&lt;br /&gt;
            box = document.createElement(&#039;div&#039;);&lt;br /&gt;
            box.className = &#039;clbi-system-doc-indicator&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta = document.createElement(&#039;div&#039;);&lt;br /&gt;
            meta.className = &#039;clbi-system-doc-meta&#039;;&lt;br /&gt;
&lt;br /&gt;
            label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            label.className = &#039;clbi-system-doc-label&#039;;&lt;br /&gt;
            label.textContent = &#039;SYSTEM DOCUMENT&#039;;&lt;br /&gt;
&lt;br /&gt;
            type = document.createElement(&#039;span&#039;);&lt;br /&gt;
            type.className = &#039;clbi-system-doc-type&#039;;&lt;br /&gt;
&lt;br /&gt;
            title = document.createElement(&#039;div&#039;);&lt;br /&gt;
            title.className = &#039;clbi-system-doc-title&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta.appendChild(label);&lt;br /&gt;
            meta.appendChild(type);&lt;br /&gt;
            box.appendChild(meta);&lt;br /&gt;
            box.appendChild(title);&lt;br /&gt;
            row.appendChild(box);&lt;br /&gt;
&lt;br /&gt;
            anchor = getSystemDocOutputForShell();&lt;br /&gt;
            main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (anchor &amp;amp;&amp;amp; anchor.parentNode) {&lt;br /&gt;
                anchor.parentNode.insertBefore(row, anchor);&lt;br /&gt;
            } else if (main) {&lt;br /&gt;
                main.insertBefore(row, main.firstChild);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        type = row.querySelector(&#039;.clbi-system-doc-type&#039;);&lt;br /&gt;
        title = row.querySelector(&#039;.clbi-system-doc-title&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (type) type.textContent = ext;&lt;br /&gt;
        if (title) title.textContent = pageName;&lt;br /&gt;
&lt;br /&gt;
        renderSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var PAGE_TITLE_TARGET_SELECTORS = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var pageShellObserverStarted = false;&lt;br /&gt;
    var pageShellObserverTimer = null;&lt;br /&gt;
&lt;br /&gt;
    function setPageTitleDomHidden(hidden) {&lt;br /&gt;
        var nodes = document.querySelectorAll(PAGE_TITLE_TARGET_SELECTORS.join(&#039;,&#039;));&lt;br /&gt;
&lt;br /&gt;
        nodes.forEach(function (node) {&lt;br /&gt;
            if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
            if (hidden) {&lt;br /&gt;
                node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
                node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
                node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClasses() {&lt;br /&gt;
        var body = document.body;&lt;br /&gt;
        var isSystemPage;&lt;br /&gt;
&lt;br /&gt;
        if (!body) return;&lt;br /&gt;
&lt;br /&gt;
        isSystemPage = isBackendOrSystemPageForShell();&lt;br /&gt;
&lt;br /&gt;
        body.classList.remove(&#039;page-title-hidden&#039;, &#039;page-title-visible&#039;, &#039;backend-system-page&#039;, &#039;anecdote-namespace-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            removeSystemDocIndicatorForShell();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isAnecdoteNamespaceForShell()) {&lt;br /&gt;
            body.classList.add(&#039;anecdote-namespace-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;, &#039;backend-system-page&#039;, &#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
            renderSystemDocIndicatorForShell();&lt;br /&gt;
        } else if (isSystemPage) {&lt;br /&gt;
            body.classList.add(&#039;page-title-visible&#039;, &#039;backend-system-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(false);&lt;br /&gt;
        } else {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClassesDeferred() {&lt;br /&gt;
        applyPageShellClasses();&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 0);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 80);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startPageShellObserver() {&lt;br /&gt;
        var observer;&lt;br /&gt;
&lt;br /&gt;
        if (pageShellObserverStarted || !window.MutationObserver || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
        pageShellObserverStarted = true;&lt;br /&gt;
        observer = new MutationObserver(function (mutations) {&lt;br /&gt;
            var i;&lt;br /&gt;
            var target;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            시스템 CSS/JS 문서는 applyPageShellClasses()가 초기에 한 번&lt;br /&gt;
            인디케이터와 source viewer를 만든 뒤에는 MutationObserver가 다시&lt;br /&gt;
            같은 렌더링을 반복할 필요가 없다. 이 반복이 DevTools에서 body/요소가&lt;br /&gt;
            계속 플래시되는 직접 원인이다.&lt;br /&gt;
            SPA 전환 뒤의 처리는 loadPage()와 wikipage.content hook에서 따로 호출된다.&lt;br /&gt;
            */&lt;br /&gt;
            if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
                for (i = 0; i &amp;lt; mutations.length; i += 1) {&lt;br /&gt;
                    target = mutations[i] &amp;amp;&amp;amp; mutations[i].target;&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        target &amp;amp;&amp;amp;&lt;br /&gt;
                        target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                        (&lt;br /&gt;
                            target.id === &#039;clbi-system-source-viewer&#039; ||&lt;br /&gt;
                            target.id === &#039;clbi-system-doc-indicator-row&#039; ||&lt;br /&gt;
                            (target.closest &amp;amp;&amp;amp; target.closest(&#039;#clbi-system-source-viewer, #clbi-system-doc-indicator-row&#039;))&lt;br /&gt;
                        )&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-doc-indicator-row&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-source-viewer&#039;)&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (pageShellObserverTimer) return;&lt;br /&gt;
&lt;br /&gt;
            pageShellObserverTimer = window.setTimeout(function () {&lt;br /&gt;
                pageShellObserverTimer = null;&lt;br /&gt;
                applyPageShellClasses();&lt;br /&gt;
            }, 50);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        observer.observe(document.body, {&lt;br /&gt;
            childList: true,&lt;br /&gt;
            subtree: true&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {&lt;br /&gt;
            applyPageShellClassesDeferred();&lt;br /&gt;
            startPageShellObserver();&lt;br /&gt;
        });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyPageShellClassesDeferred();&lt;br /&gt;
        startPageShellObserver();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(applyPageShellClassesDeferred);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_PAGE_SHELL = {&lt;br /&gt;
        refresh: applyPageShellClasses,&lt;br /&gt;
        isBackendOrSystemPage: isBackendOrSystemPageForShell,&lt;br /&gt;
        isSystemAssetPage: isMediaWikiSystemAssetPageForShell,&lt;br /&gt;
        renderSystemDocIndicator: renderSystemDocIndicatorForShell,&lt;br /&gt;
        removeSystemDocIndicator: removeSystemDocIndicatorForShell,&lt;br /&gt;
        refreshSystemDocSourceViewer: renderSystemDocSourceViewerForShell&lt;br /&gt;
    };&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
function loadLangScript(done) {&lt;br /&gt;
    $.getScript(&#039;/index.php?title=미디어위키:Lang.js&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;)&lt;br /&gt;
        .done(function() {&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        })&lt;br /&gt;
        .fail(function(a, b, c) {&lt;br /&gt;
            console.error(&#039;Lang.js load failed:&#039;, b, c);&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initHalftoneBackground() {&lt;br /&gt;
    try {&lt;br /&gt;
        initWebGLHalftoneBackground();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;WebGL halftone background failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initWebGLHalftoneBackground() {&lt;br /&gt;
    var canvasId = &#039;site-halftone-bg&#039;;&lt;br /&gt;
    var existing = document.getElementById(canvasId);&lt;br /&gt;
    var canvas = existing || document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    var halftoneState = window.SiteHalftoneBackgroundState || (window.SiteHalftoneBackgroundState = { runId: 0 });&lt;br /&gt;
    var runId = halftoneState.runId + 1;&lt;br /&gt;
&lt;br /&gt;
    halftoneState.runId = runId;&lt;br /&gt;
&lt;br /&gt;
    if (!existing) {&lt;br /&gt;
        canvas.id = canvasId;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        document.body.insertBefore(canvas, document.body.firstChild || null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canvas.style.position = &#039;fixed&#039;;&lt;br /&gt;
    canvas.style.inset = &#039;0&#039;;&lt;br /&gt;
    canvas.style.width = &#039;100vw&#039;;&lt;br /&gt;
    canvas.style.height = &#039;100vh&#039;;&lt;br /&gt;
    canvas.style.pointerEvents = &#039;none&#039;;&lt;br /&gt;
    canvas.style.background = &#039;#000000&#039;;&lt;br /&gt;
    canvas.style.display = &#039;block&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!canvas.getAttribute(&#039;data-halftone-context-watch&#039;)) {&lt;br /&gt;
        canvas.setAttribute(&#039;data-halftone-context-watch&#039;, &#039;1&#039;);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;, function (event) {&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            canvas.style.display = &#039;none&#039;;&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = true;&lt;br /&gt;
                window.SiteHalftoneBackgroundState.runId += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }, false);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextrestored&#039;, function () {&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = false;&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(initWebGLHalftoneBackground, 0);&lt;br /&gt;
        }, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gl = canvas.getContext(&#039;webgl&#039;, {&lt;br /&gt;
        alpha: false,&lt;br /&gt;
        antialias: false,&lt;br /&gt;
        depth: false,&lt;br /&gt;
        stencil: false,&lt;br /&gt;
        preserveDrawingBuffer: false,&lt;br /&gt;
        powerPreference: &#039;low-power&#039;&lt;br /&gt;
    }) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!gl) {&lt;br /&gt;
        canvas.style.display = &#039;none&#039;;&lt;br /&gt;
        console.warn(&#039;WebGL background unavailable.&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexSrc = [&lt;br /&gt;
        &#039;attribute vec2 a_position;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_position, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var fragmentSrc = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_resolution;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;const float TAU = 6.28318530718;&#039;,&lt;br /&gt;
        &#039;float gaussian(float v, float r) {&#039;,&lt;br /&gt;
        &#039;  return exp(-((v * v) / max(0.0001, r * r)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float hash(vec2 p) {&#039;,&lt;br /&gt;
        &#039;  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float bucketAlpha(float a) {&#039;,&lt;br /&gt;
        &#039;  float i = floor(a * 9.0);&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 1.0) return 0.040;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 2.0) return 0.080;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 3.0) return 0.135;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 4.0) return 0.210;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 5.0) return 0.310;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 6.0) return 0.430;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 7.0) return 0.580;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 8.0) return 0.760;&#039;,&lt;br /&gt;
        &#039;  return 0.920;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 frag = gl_FragCoord.xy;&#039;,&lt;br /&gt;
        &#039;  float spacing = 5.0;&#039;,&lt;br /&gt;
        &#039;  float dotSize = 1.08;&#039;,&lt;br /&gt;
        &#039;  vec2 grid = floor(frag / spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 inCell = mod(frag, spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 dotOrigin = vec2(1.0, 1.0);&#039;,&lt;br /&gt;
        &#039;  vec2 dotCenter = dotOrigin + vec2(dotSize * 0.5);&#039;,&lt;br /&gt;
        &#039;  vec2 local = abs(inCell - dotCenter);&#039;,&lt;br /&gt;
        &#039;  float noise = hash(grid);&#039;,&lt;br /&gt;
        &#039;  float size = dotSize + noise * 0.18;&#039;,&lt;br /&gt;
        &#039;  float dotMask = 1.0 - smoothstep(size * 0.5, size * 0.5 + 0.22, max(local.x, local.y));&#039;,&lt;br /&gt;
        &#039;  vec2 uv = frag / u_resolution;&#039;,&lt;br /&gt;
        &#039;  float centerLine = 0.50 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 1.32 + 0.08) * TAU) * 0.070 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 3.18 + 0.34) * TAU) * 0.030;&#039;,&lt;br /&gt;
        &#039;  float u = uv.x - centerLine;&#039;,&lt;br /&gt;
        &#039;  float absU = abs(u);&#039;,&lt;br /&gt;
        &#039;  float sideLift = smoothstep(0.065, 0.44, absU);&#039;,&lt;br /&gt;
        &#039;  float valley = gaussian(u, 0.150);&#039;,&lt;br /&gt;
        &#039;  float t = u_time;&#039;,&lt;br /&gt;
        &#039;  float leftRibbonCenter = -0.28 + sin((uv.y * 3.20 + 0.12) * TAU) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float rightRibbonCenter = 0.27 + sin((uv.y * 2.85 + 0.56) * TAU) * 0.055;&#039;,&lt;br /&gt;
        &#039;  float leftRibbon = gaussian(u - leftRibbonCenter, 0.105);&#039;,&lt;br /&gt;
        &#039;  float rightRibbon = gaussian(u - rightRibbonCenter, 0.110);&#039;,&lt;br /&gt;
        &#039;  float foldedU = u +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 4.40 + 0.22) * TAU) * 0.050 * (0.3 + sideLift) +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 7.20 + uv.x * 1.10) * TAU) * 0.022;&#039;,&lt;br /&gt;
        &#039;  float verticalFold = pow(0.5 + 0.5 * cos(((foldedU * 3.05) + (sin(uv.y * TAU * 2.35) * 0.18)) * TAU), 2.5);&#039;,&lt;br /&gt;
        &#039;  float diagonalFold = pow(0.5 + 0.5 * cos(((foldedU * 1.80) - (uv.y * 1.12) + 0.18) * TAU), 2.1);&#039;,&lt;br /&gt;
        &#039;  float waist = gaussian(uv.y - 0.50, 0.25) * gaussian(absU - 0.20, 0.19);&#039;,&lt;br /&gt;
        &#039;  float grain = (noise - 0.5) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float staticField =&#039;,&lt;br /&gt;
        &#039;    0.055 +&#039;,&lt;br /&gt;
        &#039;    sideLift * 0.210 +&#039;,&lt;br /&gt;
        &#039;    (leftRibbon + rightRibbon) * 0.145 +&#039;,&lt;br /&gt;
        &#039;    verticalFold * (0.055 + sideLift * 0.115) +&#039;,&lt;br /&gt;
        &#039;    diagonalFold * 0.045 +&#039;,&lt;br /&gt;
        &#039;    waist * 0.060 -&#039;,&lt;br /&gt;
        &#039;    valley * 0.150 +&#039;,&lt;br /&gt;
        &#039;    grain;&#039;,&lt;br /&gt;
        &#039;  float alpha = staticField;&#039;,&lt;br /&gt;
        &#039;  alpha += 0.115 * (leftRibbon + rightRibbon) * sin(t * 0.00030 + ((uv.y * 1.9) + sideLift * 0.4) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.095 * verticalFold * (0.4 + sideLift) * sin(t * 0.00041 + ((uv.y * 2.7) + foldedU * 0.65) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.070 * waist * sin(t * 0.00053 + ((uv.y * 3.1) - absU * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.060 * (1.0 - valley) * diagonalFold * sin(t * 0.00067 + ((uv.y * 1.4) + uv.x * 0.6) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.038 * (0.35 + sideLift) * (0.35 + noise) * sin(t * 0.00079 + ((uv.y * 4.6) + noise * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha = bucketAlpha(clamp(alpha, 0.025, 0.96));&#039;,&lt;br /&gt;
        &#039;  float value = alpha * dotMask;&#039;,&lt;br /&gt;
        &#039;  gl_FragColor = vec4(vec3(0.8862745 * value), 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function compileShader(type, source) {&lt;br /&gt;
        var shader = gl.createShader(type);&lt;br /&gt;
        gl.shaderSource(shader, source);&lt;br /&gt;
        gl.compileShader(shader);&lt;br /&gt;
&lt;br /&gt;
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {&lt;br /&gt;
            console.error(&#039;WebGL shader compile error:&#039;, gl.getShaderInfoLog(shader));&lt;br /&gt;
            gl.deleteShader(shader);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return shader;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexShader = compileShader(gl.VERTEX_SHADER, vertexSrc);&lt;br /&gt;
    var fragmentShader = compileShader(gl.FRAGMENT_SHADER, fragmentSrc);&lt;br /&gt;
&lt;br /&gt;
    if (!vertexShader || !fragmentShader) return;&lt;br /&gt;
&lt;br /&gt;
    var program = gl.createProgram();&lt;br /&gt;
    gl.attachShader(program, vertexShader);&lt;br /&gt;
    gl.attachShader(program, fragmentShader);&lt;br /&gt;
    gl.linkProgram(program);&lt;br /&gt;
&lt;br /&gt;
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {&lt;br /&gt;
        console.error(&#039;WebGL program link error:&#039;, gl.getProgramInfoLog(program));&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var positionLoc = gl.getAttribLocation(program, &#039;a_position&#039;);&lt;br /&gt;
    var resolutionLoc = gl.getUniformLocation(program, &#039;u_resolution&#039;);&lt;br /&gt;
    var timeLoc = gl.getUniformLocation(program, &#039;u_time&#039;);&lt;br /&gt;
&lt;br /&gt;
    var buffer = gl.createBuffer();&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([&lt;br /&gt;
        -1, -1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
         1,  1&lt;br /&gt;
    ]), gl.STATIC_DRAW);&lt;br /&gt;
&lt;br /&gt;
    gl.useProgram(program);&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.enableVertexAttribArray(positionLoc);&lt;br /&gt;
    gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
    function resize() {&lt;br /&gt;
        var dpr = Math.min(window.devicePixelRatio || 1, 1.5);&lt;br /&gt;
        var cssW = Math.max(1, window.innerWidth || document.documentElement.clientWidth || 1);&lt;br /&gt;
        var cssH = Math.max(1, window.innerHeight || document.documentElement.clientHeight || 1);&lt;br /&gt;
        var w = Math.max(1, Math.floor(cssW * dpr));&lt;br /&gt;
        var h = Math.max(1, Math.floor(cssH * dpr));&lt;br /&gt;
&lt;br /&gt;
        if (canvas.width !== w || canvas.height !== h) {&lt;br /&gt;
            canvas.width = w;&lt;br /&gt;
            canvas.height = h;&lt;br /&gt;
            canvas.style.width = cssW + &#039;px&#039;;&lt;br /&gt;
            canvas.style.height = cssH + &#039;px&#039;;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var prefersReducedMotion = false;&lt;br /&gt;
    try {&lt;br /&gt;
        prefersReducedMotion = window.matchMedia &amp;amp;&amp;amp; window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches;&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneNode() {&lt;br /&gt;
        return document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneState() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            return &#039;none&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) {&lt;br /&gt;
            return &#039;active&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundBusy === &#039;function&#039; &amp;amp;&amp;amp; instance.isHalftoneBackgroundBusy()) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;present&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function shouldSkipHalftoneDrawForGlobe() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) return false;&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Background pause/resume — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * While the nations globe is being held, keep the last rendered halftone&lt;br /&gt;
         * frame on screen and pause the halftone clock.  The important detail is&lt;br /&gt;
         * that this is not a wall-clock catch-up: skipped seconds are not replayed&lt;br /&gt;
         * or fast-forwarded when the pointer is released.&lt;br /&gt;
         */&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) return true;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; instance.dragging) return true;&lt;br /&gt;
&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundHardBusy === &#039;function&#039;) {&lt;br /&gt;
            return instance.isHalftoneBackgroundHardBusy();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) return true;&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFrameInterval() {&lt;br /&gt;
        if (prefersReducedMotion) {&lt;br /&gt;
            return 1000;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Nations background parity — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * The nations page used to permanently lower the halftone cadence while&lt;br /&gt;
         * the globe existed.  Profiling showed that the steady halftone draw is&lt;br /&gt;
         * normally cheap; the real contention happens during globe drag, first&lt;br /&gt;
         * WebGL texture upload, and long layout/render tasks.  Keep the same&lt;br /&gt;
         * full-quality cadence as ordinary pages, and skip only the frames that&lt;br /&gt;
         * would directly collide with an active/busy globe moment.&lt;br /&gt;
         */&lt;br /&gt;
        return 66;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var lastFrame = 0;&lt;br /&gt;
    var startTime = performance.now();&lt;br /&gt;
    var visualTime = 0;&lt;br /&gt;
    var lastVisualNow = 0;&lt;br /&gt;
&lt;br /&gt;
    function draw(now) {&lt;br /&gt;
        var state = getNationsGlobeHalftoneState();&lt;br /&gt;
        var interval = getFrameInterval();&lt;br /&gt;
        var perf = window.InteractionPerf;&lt;br /&gt;
        var delta;&lt;br /&gt;
&lt;br /&gt;
        if (!lastVisualNow) {&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
        }&lt;br /&gt;
        delta = Math.max(0, Math.min(120, now - lastVisualNow));&lt;br /&gt;
        lastVisualNow = now;&lt;br /&gt;
        visualTime += delta;&lt;br /&gt;
&lt;br /&gt;
        function runDraw() {&lt;br /&gt;
            resize();&lt;br /&gt;
&lt;br /&gt;
            gl.clearColor(0, 0, 0, 1);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.uniform2f(resolutionLoc, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform1f(timeLoc, visualTime);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLES, 0, 6);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (perf &amp;amp;&amp;amp; typeof perf.measureSync === &#039;function&#039;) {&lt;br /&gt;
            return perf.measureSync(&#039;background halftone draw&#039;, { globeState: state, interval: interval }, runDraw);&lt;br /&gt;
        }&lt;br /&gt;
        return runDraw();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(now) {&lt;br /&gt;
        if (halftoneState.runId !== runId || halftoneState.contextLost) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(render);&lt;br /&gt;
&lt;br /&gt;
        if (document.hidden) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (now - lastFrame &amp;lt; getFrameInterval()) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (shouldSkipHalftoneDrawForGlobe()) {&lt;br /&gt;
            lastFrame = now;&lt;br /&gt;
            halftoneState.lastGlobeBusySkipAt = now;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Pause the halftone clock while a hard-busy globe frame owns the&lt;br /&gt;
             * budget.  Without this, the next visible background frame catches&lt;br /&gt;
             * up to wall-clock time and the pattern appears to skip forward.&lt;br /&gt;
             * Updating lastVisualNow here makes the animation resume from the&lt;br /&gt;
             * last visible moment instead of replaying the hidden gap.&lt;br /&gt;
             */&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        draw(now);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    draw(performance.now());&lt;br /&gt;
    requestAnimationFrame(render);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SVG_BELL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_BELL_DOT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell-dot&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M11.68 2.009A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673c-.824-.85-1.678-1.731-2.21-3.348&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;18&amp;quot; cy=&amp;quot;5&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LIST = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-list&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 5h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 12h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 19h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 5h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 12h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 19h13&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LANGUAGES = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-languages&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;m5 8 6 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m4 14 6-6 2-3&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M2 5h12&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 2h1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m22 22-5-10-5 10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 18h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_POWER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-power&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 2v10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.4 6.6a9 9 0 1 1-12.77.04&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SETTINGS = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-settings&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_TEXT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-text&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 8h8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 12h10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 16h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_EYE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-eye&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_NEWSPAPER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-newspaper&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M15 18h-5&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 14h-8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-4 0v-9a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;8&amp;quot; height=&amp;quot;4&amp;quot; x=&amp;quot;10&amp;quot; y=&amp;quot;6&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_GREAT_WALL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-great-wall lucide lucide-paint-roller&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;rect width=&amp;quot;16&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;2&amp;quot; y=&amp;quot;2&amp;quot; rx=&amp;quot;2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M10 16v-2a2 2 0 0 1 2-2h8a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;4&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;8&amp;quot; y=&amp;quot;16&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_TROPHY = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-trophy&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 9h1.5a1 1 0 0 0 0-5H18&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9H4.5a1 1 0 0 1 0-5H6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_PACKAGE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-package&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 3v6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M16.76 3a2 2 0 0 1 1.8 1.1l2.23 4.479a2 2 0 0 1 .21.891V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9.472a2 2 0 0 1 .211-.894L5.45 4.1A2 2 0 0 1 7.24 3z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.054 9.013h17.893&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
var PROFILE_RENDER_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function invalidateProfileRender() {&lt;br /&gt;
    PROFILE_RENDER_TOKEN++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    initHalftoneBackground();&lt;br /&gt;
&lt;br /&gt;
// ── 하단 Plank 단축키 가이드 ──&lt;br /&gt;
function escapeClbiBottomGuideHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiBottomGuidePageName() {&lt;br /&gt;
    var pageName = window.mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) {&lt;br /&gt;
        pageName = window.location.pathname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return String(pageName)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsShortcutContext() {&lt;br /&gt;
    var pageName = readClbiBottomGuidePageName();&lt;br /&gt;
&lt;br /&gt;
    return pageName === &#039;시대&#039; ||&lt;br /&gt;
        pageName === &#039;Era&#039; ||&lt;br /&gt;
        !!document.querySelector(&#039;.clbi-nations-panel-stack, .clbi-nations-globe-window, .clbi-nations-tabpanel&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiWikiEditShortcutContext() {&lt;br /&gt;
    var action = window.mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgAction&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    var search = String(window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
    return action === &#039;edit&#039; ||&lt;br /&gt;
        action === &#039;submit&#039; ||&lt;br /&gt;
        /(?:^|[?&amp;amp;])action=(?:edit|submit)(?:&amp;amp;|$)/.test(search) ||&lt;br /&gt;
        !!document.querySelector(&#039;#editform, #wpSave, input[name=&amp;quot;wpSave&amp;quot;], button[name=&amp;quot;wpSave&amp;quot;]&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function isClbiNationListManagerShortcutContext() {&lt;br /&gt;
    return !!document.querySelector(&#039;.nation-list-manager&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiBottomShortcutItems() {&lt;br /&gt;
    if (isClbiWikiEditShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;변경사항 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationListManagerShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;국가 목록 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Q&#039;, label: &#039;이전 시대&#039; },&lt;br /&gt;
            { key: &#039;E&#039;, label: &#039;다음 시대&#039; },&lt;br /&gt;
            { key: &#039;Shift+Q&#039;, label: &#039;이전 연도&#039; },&lt;br /&gt;
            { key: &#039;Shift+E&#039;, label: &#039;다음 연도&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+Q&#039;, label: &#039;이전 대륙&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+E&#039;, label: &#039;다음 대륙&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return [];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderClbiBottomShortcutGuide() {&lt;br /&gt;
    var guide = document.getElementById(&#039;clbi-bottom-shortcut-guide&#039;);&lt;br /&gt;
    var items;&lt;br /&gt;
    var html;&lt;br /&gt;
&lt;br /&gt;
    if (!guide) return;&lt;br /&gt;
&lt;br /&gt;
    items = getClbiBottomShortcutItems();&lt;br /&gt;
&lt;br /&gt;
    if (!items.length) {&lt;br /&gt;
        guide.classList.add(&#039;is-empty&#039;);&lt;br /&gt;
        guide.innerHTML = &#039;&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    guide.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    html = &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-list&amp;quot;&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        html += &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-key&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.key) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-label&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    html += &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    guide.innerHTML = html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiBottomPlankHtml(wrapId, navId, mainId) {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-bottom-shortcut-guide&amp;quot; class=&amp;quot;is-empty&amp;quot; aria-label=&amp;quot;단축키 안내&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
&lt;br /&gt;
function getClbiNationsTabAtPointer(tabpanel) {&lt;br /&gt;
    var element;&lt;br /&gt;
    var tab;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return null;&lt;br /&gt;
    if (CLBI_NATIONS_LAST_POINTER_X === null || CLBI_NATIONS_LAST_POINTER_Y === null) return null;&lt;br /&gt;
    if (typeof document.elementFromPoint !== &#039;function&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
    element = document.elementFromPoint(CLBI_NATIONS_LAST_POINTER_X, CLBI_NATIONS_LAST_POINTER_Y);&lt;br /&gt;
    if (!element) return null;&lt;br /&gt;
&lt;br /&gt;
    tab = element.closest ? element.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (!tab || !tabpanel.contains(tab)) return null;&lt;br /&gt;
&lt;br /&gt;
    return tab;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateClbiNationsPointerHover(tabpanel, forceSuppress) {&lt;br /&gt;
    var tab = getClbiNationsTabAtPointer(tabpanel);&lt;br /&gt;
    var suppressContinent;&lt;br /&gt;
    var tabContinent;&lt;br /&gt;
    var tabIsActive;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
&lt;br /&gt;
    if (!tab) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabContinent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    tabIsActive = tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (tabIsActive) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    suppressContinent = tabpanel.getAttribute(&#039;data-clbi-hover-suppress-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (forceSuppress || !suppressContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-hover-suppress-continent&#039;, tabContinent);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (suppressContinent === tabContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * The pointer actually moved onto another tab after the keyboard switch.&lt;br /&gt;
     * At that point this is no longer stale browser :hover; let normal hover work.&lt;br /&gt;
     */&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateAllClbiNationsPointerHovers() {&lt;br /&gt;
    var panels = document.querySelectorAll(&#039;.clbi-nations-tabpanel.is-keyboard-switching&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        if (isClbiNationsPanelOwnedTabpanel(tabpanel)) return;&lt;br /&gt;
        validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsPanelOwnedTabpanel(tabpanel) {&lt;br /&gt;
    /*&lt;br /&gt;
     * Mouse continent tab regression guard.&lt;br /&gt;
     * -------------------------------------&lt;br /&gt;
     * Detached SPA preparation serializes attributes/classes but it cannot&lt;br /&gt;
     * serialize DOM event listeners or JS properties.  The NationsPanel fix&lt;br /&gt;
     * therefore uses the DOM property CLBI_NationsPanelOwned /&lt;br /&gt;
     * CLBI_NationsTabPanelBound as the real ownership marker.&lt;br /&gt;
     *&lt;br /&gt;
     * Do NOT treat data-nations-tabpanel-ready or clbi-nations-continent-cache&lt;br /&gt;
     * as ownership here.  Those can survive innerHTML insertion while the real&lt;br /&gt;
     * click listeners were lost, causing Common.js to delegate to a panel that&lt;br /&gt;
     * NationsPanel has not rebound yet.  The fallback below must remain able to&lt;br /&gt;
     * handle mouse clicks until NationsPanel reclaims the live DOM node.&lt;br /&gt;
     */&lt;br /&gt;
    return !!(tabpanel &amp;amp;&amp;amp; tabpanel.CLBI_NationsPanelOwned);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsLiveContinentActive(tabpanel, continent) {&lt;br /&gt;
    var tab;&lt;br /&gt;
    var panel;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !continent) return false;&lt;br /&gt;
&lt;br /&gt;
    tab = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panel = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent-panel&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return !!(&lt;br /&gt;
        tab &amp;amp;&amp;amp;&lt;br /&gt;
        panel &amp;amp;&amp;amp;&lt;br /&gt;
        (tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.classList.contains(&#039;is-active&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.getAttribute(&#039;aria-hidden&#039;) !== &#039;true&#039; &amp;amp;&amp;amp;&lt;br /&gt;
        !panel.hasAttribute(&#039;hidden&#039;)&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function activateClbiNationsContinent(tabpanel, targetContinent, options) {&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var panels;&lt;br /&gt;
    var skipOwner = !!(options &amp;amp;&amp;amp; options.skipOwner);&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !targetContinent) return false;&lt;br /&gt;
&lt;br /&gt;
    if (!skipOwner &amp;amp;&amp;amp; isClbiNationsPanelOwnedTabpanel(tabpanel)) {&lt;br /&gt;
        var owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
&lt;br /&gt;
        if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (owner.activateContinent(targetContinent, {&lt;br /&gt;
                    source: &#039;common-legacy-click&#039;,&lt;br /&gt;
                    panel: tabpanel&lt;br /&gt;
                })) {&lt;br /&gt;
                    return true;&lt;br /&gt;
                }&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Ownership markers can be stale during SPA/hydration edge cases.  If&lt;br /&gt;
         * the owner API is missing or refuses the live panel, do not drop the&lt;br /&gt;
         * mouse click.  Fall through to the local fallback so pointer users are&lt;br /&gt;
         * never left with keyboard-only continent tabs.&lt;br /&gt;
         */&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    panels = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
    if (!tabs.length || !panels.length) return false;&lt;br /&gt;
&lt;br /&gt;
    tabs.forEach(function (tab) {&lt;br /&gt;
        var active = tab.getAttribute(&#039;data-continent&#039;) === targetContinent;&lt;br /&gt;
        tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
        tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panels.forEach(function (panel) {&lt;br /&gt;
        var active = panel.getAttribute(&#039;data-continent-panel&#039;) === targetContinent;&lt;br /&gt;
        panel.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
        if (active) {&lt;br /&gt;
            panel.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            panel.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.sync === &#039;function&#039;) window.CLBI_DECORATIONS.sync();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    validateClbiNationsPointerHover(tabpanel, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
    tabpanel.classList.remove(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
    tabpanel.removeAttribute(&#039;data-clbi-hover-suppress-continent&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveClbiNationsContinent(direction) {&lt;br /&gt;
    var tabpanel = document.querySelector(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var activeIndex;&lt;br /&gt;
    var nextIndex;&lt;br /&gt;
    var target;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return false;&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsPanelOwnedTabpanel(tabpanel) &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI_NATIONS_PANEL &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI_NATIONS_PANEL.moveContinent === &#039;function&#039;) {&lt;br /&gt;
        return !!window.CLBI_NATIONS_PANEL.moveContinent(direction, {&lt;br /&gt;
            source: &#039;common-legacy-keyboard&#039;,&lt;br /&gt;
            panel: tabpanel&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
    activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
        return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
    nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
    target = tabs[nextIndex].getAttribute(&#039;data-continent&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (activateClbiNationsContinent(tabpanel, target)) {&lt;br /&gt;
        setClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
        }, 0);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsTabpanelControls(root) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var panels = scope.querySelectorAll(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        /*&lt;br /&gt;
         * Common.js is only a safety fallback for continent tabs; NationsPanel&lt;br /&gt;
         * is the primary owner.  This guard must be a DOM property, not a&lt;br /&gt;
         * serialized attribute.  SPA warm prepaint can preserve&lt;br /&gt;
         * data-clbi-nations-tabs-ready=&amp;quot;1&amp;quot; while losing the actual event&lt;br /&gt;
         * listeners, which was the root cause of mouse clicks no longer moving&lt;br /&gt;
         * continents while keyboard shortcuts still worked.&lt;br /&gt;
         */&lt;br /&gt;
        if (tabpanel.CLBI_NationsCommonTabsBound) return;&lt;br /&gt;
        tabpanel.CLBI_NationsCommonTabsBound = true;&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-nations-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerdown&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
            if (activateClbiNationsContinent(tabpanel, tab.getAttribute(&#039;data-continent&#039;))) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
            var handled = false;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsContinent(-1);&lt;br /&gt;
            else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsContinent(1);&lt;br /&gt;
&lt;br /&gt;
            if (handled) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                event.stopPropagation();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function handleClbiNationsContinentDocumentClick(event) {&lt;br /&gt;
    var target;&lt;br /&gt;
    var tab;&lt;br /&gt;
    var tabpanel;&lt;br /&gt;
    var continent;&lt;br /&gt;
    var owner;&lt;br /&gt;
    var handled = false;&lt;br /&gt;
&lt;br /&gt;
    if (!event || event.__clbiNationsContinentHandled) return;&lt;br /&gt;
    target = event.target;&lt;br /&gt;
    if (!target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
    tab = target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;);&lt;br /&gt;
    if (!tab) return;&lt;br /&gt;
&lt;br /&gt;
    tabpanel = tab.closest ? tab.closest(&#039;.clbi-nations-tabpanel&#039;) : null;&lt;br /&gt;
    if (!tabpanel || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
    continent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    if (!continent) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Final live-DOM mouse delegate.&lt;br /&gt;
     * --------------------------------&lt;br /&gt;
     * The continent tab UI has two initialization phases: detached preparation&lt;br /&gt;
     * for seamless SPA entry, then live DOM hydration.  Detached preparation can&lt;br /&gt;
     * leave serialized &amp;quot;ready&amp;quot; attributes behind while losing event listeners.&lt;br /&gt;
     * Keyboard shortcuts still work because they call the public NationsPanel&lt;br /&gt;
     * API directly, but mouse clicks depend on live listeners.  This capturing&lt;br /&gt;
     * delegate is intentionally independent of per-panel ready attributes: it&lt;br /&gt;
     * always resolves the clicked live tab and hands it to NationsPanel first,&lt;br /&gt;
     * then falls back to the local Common.js switcher if needed.&lt;br /&gt;
     */&lt;br /&gt;
    event.__clbiNationsContinentHandled = true;&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
    owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
    if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
        try {&lt;br /&gt;
            handled = !!owner.activateContinent(continent, {&lt;br /&gt;
                source: &#039;mouse-click-live-delegate&#039;,&lt;br /&gt;
                panel: tabpanel&lt;br /&gt;
            });&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Owner API stale-cache guard.&lt;br /&gt;
         *&lt;br /&gt;
         * A previous regression came from NationsPanel returning true after it&lt;br /&gt;
         * updated cached/detached tab nodes instead of the clicked live tab. A&lt;br /&gt;
         * true return value alone is therefore not enough for mouse input. Verify&lt;br /&gt;
         * the live DOM that received the click. If it did not become active,&lt;br /&gt;
         * bypass the owner and run the Common.js live query fallback directly.&lt;br /&gt;
         */&lt;br /&gt;
        if (handled &amp;amp;&amp;amp; !isClbiNationsLiveContinentActive(tabpanel, continent)) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!handled) {&lt;br /&gt;
        handled = activateClbiNationsContinent(tabpanel, continent, { skipOwner: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (handled) {&lt;br /&gt;
        try { playStaticSound(); } catch (err2) {}&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) event.stopImmediatePropagation();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsGlobalClickDelegate() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-global-click-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-global-click-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleClbiNationsContinentDocumentClick, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsPointerHoverValidation() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointermove&#039;, function (event) {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = event.clientX;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = event.clientY;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiBottomShortcutSystem(root) {&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsGlobalClickDelegate();&lt;br /&gt;
    initClbiNationsTabpanelControls(root || document);&lt;br /&gt;
    initClbiNationsPointerHoverValidation();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// ── 상·하단 네비게이션 바 ──&lt;br /&gt;
function buildClbiNavHtml(position) {&lt;br /&gt;
    var isBottom = position === &#039;bottom&#039;;&lt;br /&gt;
    var base = isBottom ? &#039;clbi-bottom&#039; : &#039;clbi-top&#039;;&lt;br /&gt;
    var shortBase = isBottom ? &#039;clbi-bnav&#039; : &#039;clbi-tnav&#039;;&lt;br /&gt;
    var wrapId = base + &#039;-nav-wrap&#039;;&lt;br /&gt;
    var navId = base + &#039;-nav&#039;;&lt;br /&gt;
    var mainId = base + &#039;-nav-main&#039;;&lt;br /&gt;
    var tabsId = base + &#039;-nav-tabs&#039;;&lt;br /&gt;
    var searchId = base + &#039;-nav-search&#039;;&lt;br /&gt;
    var inputId = isBottom ? &#039;clbi-bottom-search-input&#039; : &#039;clbi-top-search-input&#039;;&lt;br /&gt;
    var worldId = shortBase + &#039;-worldbuilding&#039;;&lt;br /&gt;
    var subId = isBottom ? &#039;clbi-bottom-sub-worldbuilding&#039; : &#039;clbi-sub-worldbuilding&#039;;&lt;br /&gt;
    var subInnerId = subId + &#039;-inner&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (isBottom) {&lt;br /&gt;
        return buildClbiBottomPlankHtml(wrapId, navId, mainId);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + tabsId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/대문&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-main-menu-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;메인 메뉴&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-project-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;프로젝트&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-top-nav-item&amp;quot; id=&amp;quot;&#039; + worldId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-worldbuilding-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;세계관&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-arrow&amp;quot;&amp;gt;▾&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    (isBottom ? &#039;&#039; : (&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;&#039; + searchId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;&#039; + inputId + &#039;&amp;quot; placeholder=&amp;quot;검색...&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                    )) +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + subId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + subInnerId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-tnav-sub-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/시대&amp;quot;&amp;gt;시대&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/설정&amp;quot;&amp;gt;설정&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/기업_및_공동체&amp;quot;&amp;gt;기업 및 공동체&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/군_정치집단&amp;quot;&amp;gt;군, 정치집단&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/인물&amp;quot;&amp;gt;인물&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeClbiShellDomOrder() {&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var canvas = document.getElementById(&#039;site-halftone-bg&#039;);&lt;br /&gt;
    var anchor;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var topRect;&lt;br /&gt;
    var wrapperRect;&lt;br /&gt;
    var needsRecovery;&lt;br /&gt;
    var expectedWrapperTop;&lt;br /&gt;
    var host;&lt;br /&gt;
&lt;br /&gt;
    if (!contentWrapper || !topNav || !bottomNav || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    CLBI shell can live inside a Liberty &amp;lt;section&amp;gt;.  Some skins/layouts give that&lt;br /&gt;
    section a flow context that lets the top nav visually overlap the content&lt;br /&gt;
    wrapper even when DOM sibling order is correct.  Mark the common parent and&lt;br /&gt;
    let Layout.css force a simple vertical flow for the shell.&lt;br /&gt;
    */&lt;br /&gt;
    host = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp;&lt;br /&gt;
        contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? contentWrapper.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (host) {&lt;br /&gt;
        host.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    viewportH = window.innerHeight || document.documentElement.clientHeight || 0;&lt;br /&gt;
    topRect = topNav.getBoundingClientRect();&lt;br /&gt;
    wrapperRect = contentWrapper.getBoundingClientRect();&lt;br /&gt;
    expectedWrapperTop = topRect.bottom + 8;&lt;br /&gt;
&lt;br /&gt;
    needsRecovery = false;&lt;br /&gt;
&lt;br /&gt;
    if (viewportH &amp;gt; 0) {&lt;br /&gt;
        if (topRect.top &amp;gt;= viewportH * 0.55) needsRecovery = true;&lt;br /&gt;
        if (wrapperRect.top &amp;gt;= viewportH * 0.60) needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (topRect.top &amp;gt; 240 || wrapperRect.top &amp;gt; 320) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (wrapperRect.top &amp;lt; expectedWrapperTop - 1) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!needsRecovery) {&lt;br /&gt;
        document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    anchor = canvas &amp;amp;&amp;amp; canvas.parentNode === document.body&lt;br /&gt;
        ? canvas.nextSibling&lt;br /&gt;
        : document.body.firstChild;&lt;br /&gt;
&lt;br /&gt;
    document.body.insertBefore(topNav, anchor);&lt;br /&gt;
    document.body.insertBefore(contentWrapper, topNav.nextSibling);&lt;br /&gt;
    document.body.insertBefore(bottomNav, contentWrapper.nextSibling);&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.normalizeClbiShellDomOrder = normalizeClbiShellDomOrder;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureClbiVerticalScaleHost() {&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var host;&lt;br /&gt;
    var existing;&lt;br /&gt;
    var parent;&lt;br /&gt;
&lt;br /&gt;
    if (!topNav || !contentWrapper || !bottomNav || !document.body) return null;&lt;br /&gt;
&lt;br /&gt;
    existing = document.getElementById(&#039;clbi-shell-scale-host&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (existing &amp;amp;&amp;amp; existing.contains(topNav) &amp;amp;&amp;amp; existing.contains(contentWrapper) &amp;amp;&amp;amp; existing.contains(bottomNav)) {&lt;br /&gt;
        existing.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        return existing;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    parent = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp; contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? topNav.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (parent &amp;amp;&amp;amp; parent !== document.body) {&lt;br /&gt;
        parent.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        parent.id = parent.id || &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
        return parent;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host = existing || document.createElement(&#039;div&#039;);&lt;br /&gt;
    host.id = &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
    host.className = &#039;clbi-shell-host&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!host.parentNode) {&lt;br /&gt;
        document.body.insertBefore(host, topNav);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host.appendChild(topNav);&lt;br /&gt;
    host.appendChild(contentWrapper);&lt;br /&gt;
    host.appendChild(bottomNav);&lt;br /&gt;
&lt;br /&gt;
    return host;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiRootPx(name, fallback) {&lt;br /&gt;
    var raw = &#039;&#039;;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        raw = getComputedStyle(document.documentElement).getPropertyValue(name);&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    value = parseFloat(raw);&lt;br /&gt;
    return isFinite(value) &amp;amp;&amp;amp; value &amp;gt; 0 ? value : fallback;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiShellVerticalScale() {&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    var host;&lt;br /&gt;
    var topInner;&lt;br /&gt;
    var bottomInner;&lt;br /&gt;
    var thresholdH;&lt;br /&gt;
    var gap;&lt;br /&gt;
    var outerGapTotal;&lt;br /&gt;
    var baseStageH;&lt;br /&gt;
    var stageW;&lt;br /&gt;
    var stageH;&lt;br /&gt;
    var viewportW;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var availableW;&lt;br /&gt;
    var availableH;&lt;br /&gt;
    var topH;&lt;br /&gt;
    var bottomH;&lt;br /&gt;
    var contentH;&lt;br /&gt;
    var widthScale;&lt;br /&gt;
    var heightScale;&lt;br /&gt;
    var scale;&lt;br /&gt;
    var shouldScale;&lt;br /&gt;
&lt;br /&gt;
    if (!root || !body) return;&lt;br /&gt;
&lt;br /&gt;
    host = ensureClbiVerticalScaleHost();&lt;br /&gt;
    if (!host) return;&lt;br /&gt;
&lt;br /&gt;
    thresholdH = readClbiRootPx(&#039;--clbi-vertical-scale-threshold-h&#039;, 1080);&lt;br /&gt;
    gap = readClbiRootPx(&#039;--layout-gap&#039;, 8);&lt;br /&gt;
    outerGapTotal = gap * 2;&lt;br /&gt;
    baseStageH = Math.max(420, thresholdH - outerGapTotal);&lt;br /&gt;
    stageW = readClbiRootPx(&#039;--layout-shell-w&#039;, 1880);&lt;br /&gt;
&lt;br /&gt;
    viewportW = Math.max(320, window.innerWidth || document.documentElement.clientWidth || stageW);&lt;br /&gt;
    viewportH = Math.max(320, window.innerHeight || document.documentElement.clientHeight || thresholdH);&lt;br /&gt;
    availableW = Math.max(240, viewportW - outerGapTotal);&lt;br /&gt;
    availableH = Math.max(240, viewportH - outerGapTotal);&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    평상시에는 기존 세로 채움 레이아웃을 기준으로 삼는다.&lt;br /&gt;
    세로가 기준점보다 작아지는 경우에는 기준 높이를 고정점으로 삼고,&lt;br /&gt;
    가로만 부족한 경우에는 현재 사용 가능한 세로 높이를 고정점으로 삼는다.&lt;br /&gt;
    이렇게 해야 가로 부족으로 scale에 들어갈 때 본문 높이가 갑자기 접히지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    stageH = availableH &amp;gt;= baseStageH ? availableH : baseStageH;&lt;br /&gt;
&lt;br /&gt;
    topInner = document.getElementById(&#039;clbi-top-nav&#039;);&lt;br /&gt;
    bottomInner = document.getElementById(&#039;clbi-bottom-nav&#039;);&lt;br /&gt;
&lt;br /&gt;
    topH = topInner ? Math.ceil(topInner.offsetHeight || topInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    bottomH = bottomInner ? Math.ceil(bottomInner.offsetHeight || bottomInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    contentH = Math.max(360, Math.floor(stageH - topH - bottomH - (gap * 2)));&lt;br /&gt;
&lt;br /&gt;
    widthScale = availableW &amp;lt; stageW ? availableW / stageW : 1;&lt;br /&gt;
    heightScale = availableH &amp;lt; baseStageH ? availableH / baseStageH : 1;&lt;br /&gt;
    scale = Math.min(1, widthScale, heightScale);&lt;br /&gt;
    scale = Math.max(0.50, Math.min(1, Math.floor(scale * 1000) / 1000));&lt;br /&gt;
    shouldScale = scale &amp;lt; 0.999;&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-w&#039;, stageW + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-h&#039;, Math.floor(stageH) + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-content-h&#039;, contentH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-shell-scale&#039;, String(scale));&lt;br /&gt;
&lt;br /&gt;
    body.classList.toggle(&#039;clbi-shell-vertical-scale&#039;, shouldScale);&lt;br /&gt;
&lt;br /&gt;
    if (shouldScale) {&lt;br /&gt;
        resetLeftRecentAdaptiveState();&lt;br /&gt;
        resetLeftBillboardAdaptiveState();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
window.updateClbiShellVerticalScale = updateClbiShellVerticalScale;&lt;br /&gt;
&lt;br /&gt;
var $contentWrapper = $(&#039;.content-wrapper&#039;).first();&lt;br /&gt;
&lt;br /&gt;
if ($contentWrapper.length) {&lt;br /&gt;
    $(&#039;#clbi-top-nav-wrap, #clbi-bottom-nav-wrap&#039;).remove();&lt;br /&gt;
    $contentWrapper.before(buildClbiNavHtml(&#039;top&#039;));&lt;br /&gt;
    $contentWrapper.after(buildClbiNavHtml(&#039;bottom&#039;));&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsTabpanelControls(document);&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
function runClbiShellMetricsBatch() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var topH = 0;&lt;br /&gt;
    var bottomH = 0;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    if (top) {&lt;br /&gt;
        topH = Math.ceil(top.getBoundingClientRect().height || top.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (bottom) {&lt;br /&gt;
        bottomH = Math.ceil(bottom.getBoundingClientRect().height || bottom.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-top-nav-outer-h&#039;, topH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-bottom-nav-outer-h&#039;, bottomH + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (typeof updateClbiShellVerticalScale === &#039;function&#039;) {&lt;br /&gt;
        updateClbiShellVerticalScale();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestClbiShellMetricsFrame() {&lt;br /&gt;
    if (CLBI_SHELL_METRICS_RAF !== null) return;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = window.requestAnimationFrame&lt;br /&gt;
        ? window.requestAnimationFrame(runClbiShellMetricsBatch)&lt;br /&gt;
        : window.setTimeout(runClbiShellMetricsBatch, 16);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiShellMetrics() {&lt;br /&gt;
    requestClbiShellMetricsFrame();&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 0);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 80);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 240);&lt;br /&gt;
}&lt;br /&gt;
function watchClbiShellMetrics() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var observer;&lt;br /&gt;
&lt;br /&gt;
    scheduleClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
    $(window).on(&#039;resize orientationchange&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    $(window).on(&#039;pageshow.clbiShellScale focus.clbiShellScale&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
        if (!document.hidden) scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
    $(window).on(&#039;resize.clbiLeftBillboard orientationchange.clbiLeftBillboard&#039;, scheduleLeftSidebarVerticalFit);&lt;br /&gt;
    $(window).on(&#039;resize.clbiRecentViewport orientationchange.clbiRecentViewport&#039;, function () { scheduleAdaptiveLeftRecentItems(); scheduleClbiContentBottomGap(); });&lt;br /&gt;
    $(window).on(&#039;resize.clbiContentBottomGap orientationchange.clbiContentBottomGap&#039;, scheduleClbiContentBottomGap);&lt;br /&gt;
&lt;br /&gt;
    if (window.ResizeObserver) {&lt;br /&gt;
        observer = new ResizeObserver(scheduleClbiShellMetrics);&lt;br /&gt;
        if (top) observer.observe(top);&lt;br /&gt;
        if (bottom) observer.observe(bottom);&lt;br /&gt;
        window.CLBI_SHELL_RESIZE_OBSERVER = observer;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindClbiWorldbuildingToggle(buttonSelector, menuSelector) {&lt;br /&gt;
    $(buttonSelector).on(&#039;click&#039;, function() {&lt;br /&gt;
        var $menu = $(menuSelector);&lt;br /&gt;
        var $btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        $menu.toggleClass(&#039;worldbuilding-open&#039;);&lt;br /&gt;
        $btn.toggleClass(&#039;clbi-tnav-active&#039;, $menu.hasClass(&#039;worldbuilding-open&#039;));&lt;br /&gt;
        scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-tnav-worldbuilding&#039;, &#039;#clbi-sub-worldbuilding&#039;);&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-bnav-worldbuilding&#039;, &#039;#clbi-bottom-sub-worldbuilding&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-top-search-input, #clbi-bottom-search-input&#039;).on(&#039;keydown&#039;, function(e) {&lt;br /&gt;
    if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
        var q = $(this).val().trim();&lt;br /&gt;
        if (q) window.location.href = &#039;/index.php?search=&#039; + encodeURIComponent(q);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
        initClbiBottomShortcutSystem($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
watchClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// 페이지 전환 사운드&lt;br /&gt;
var transitionSound = new Audio(&#039;/index.php?title=특수:Redirect/file/Sfx-ui-001.mp3&#039;);&lt;br /&gt;
&lt;br /&gt;
(function() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
    transitionSound.volume = sfxOn ? master * sfx : 0;&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
function playStaticSound() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!sfxOn) return;&lt;br /&gt;
&lt;br /&gt;
    transitionSound.volume = master * sfx;&lt;br /&gt;
    transitionSound.currentTime = 0;&lt;br /&gt;
    transitionSound.play();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 현재 언어 감지&lt;br /&gt;
function getCurrentLang() {&lt;br /&gt;
    var langData = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    return langData ? (langData.getAttribute(&#039;data-lang&#039;) || &#039;ko&#039;) : &#039;ko&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizePageName(value) {&lt;br /&gt;
    return String(value || &#039;&#039;)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildWikiPath(title) {&lt;br /&gt;
    return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLangShortCode(lang) {&lt;br /&gt;
    var map = { ko: &#039;KR&#039;, en: &#039;EN&#039;, zh: &#039;ZH&#039;, ja: &#039;JA&#039;, ru: &#039;RU&#039;, es: &#039;ES&#039; };&lt;br /&gt;
    return map[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLanguageTargetTitle(lang) {&lt;br /&gt;
    var data = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    if (!data || !lang) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var keys = [&lt;br /&gt;
        &#039;data-&#039; + lang,&lt;br /&gt;
        &#039;data-page-&#039; + lang,&lt;br /&gt;
        &#039;data-title-&#039; + lang,&lt;br /&gt;
        &#039;data-target-&#039; + lang,&lt;br /&gt;
        &#039;data-lang-&#039; + lang&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var value = data.getAttribute(keys[i]);&lt;br /&gt;
        if (value) return value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeClbiHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var SIDEBAR_LANG_SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_TITLE = &#039;MediaWiki:LanguageStatus.json&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;English&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;Русский&#039;,&lt;br /&gt;
    es: &#039;Español&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_DIAL_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;ENG&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;РУС&#039;,&lt;br /&gt;
    es: &#039;ESP&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_VALUES = {&lt;br /&gt;
    available: true,&lt;br /&gt;
    wip: true,&lt;br /&gt;
    unavailable: true&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageStatusRegistry = {};&lt;br /&gt;
var sidebarLanguageStatusLoaded = false;&lt;br /&gt;
var sidebarLanguageStatusLoading = false;&lt;br /&gt;
var sidebarLanguageStatusCallbacks = [];&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageState = {&lt;br /&gt;
    order: [&#039;ko&#039;, &#039;en&#039;, &#039;zh&#039;, &#039;ja&#039;, &#039;ru&#039;, &#039;es&#039;],&lt;br /&gt;
    currentLang: &#039;ko&#039;,&lt;br /&gt;
    baseIndex: 0,&lt;br /&gt;
    selectedIndex: 0,&lt;br /&gt;
    rotation: 0,&lt;br /&gt;
    dragging: false,&lt;br /&gt;
    dragMoved: false,&lt;br /&gt;
    dragStartX: 0,&lt;br /&gt;
    dragStartY: 0,&lt;br /&gt;
    dragStartRotation: 0,&lt;br /&gt;
    dragAxis: null,&lt;br /&gt;
    pointerCaptured: false,&lt;br /&gt;
    lastX: 0,&lt;br /&gt;
    lastTime: 0,&lt;br /&gt;
    releaseVelocity: 0,&lt;br /&gt;
    suppressClickUntil: 0,&lt;br /&gt;
    raf: null,&lt;br /&gt;
    pendingRotation: 0,&lt;br /&gt;
    snapTimer: null,&lt;br /&gt;
    inertiaRaf: null,&lt;br /&gt;
    navigateTimer: null,&lt;br /&gt;
    bound: false,&lt;br /&gt;
    boundElement: null,&lt;br /&gt;
    rotor: null,&lt;br /&gt;
    cx: 101,&lt;br /&gt;
    cy: 119,&lt;br /&gt;
    outerR: 109,&lt;br /&gt;
    innerR: 28,&lt;br /&gt;
    sectorAngle: 30,&lt;br /&gt;
    halfSector: 15,&lt;br /&gt;
    repeats: 8,&lt;br /&gt;
    dragSensitivity: 0.58,&lt;br /&gt;
    maxSpinVelocity: 1.75,&lt;br /&gt;
    minSpinVelocity: 0.055,&lt;br /&gt;
    spinDecel: 0.00185&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function createSidebarLanguageSvgEl(tag) {&lt;br /&gt;
    return document.createElementNS(SIDEBAR_LANG_SVG_NS, tag);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageIndex(index) {&lt;br /&gt;
    var length = sidebarLanguageState.order.length;&lt;br /&gt;
    var normalized = index % length;&lt;br /&gt;
    return normalized &amp;lt; 0 ? normalized + length : normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageDialName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_DIAL_LABELS[lang] || getSidebarLanguageName(lang);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageStatusValue(value) {&lt;br /&gt;
    value = String(value == null ? &#039;&#039; : value).toLowerCase().trim();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_STATUS_VALUES[value] ? value : &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusPageKey() {&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
&lt;br /&gt;
    return normalized || raw || &#039;대문&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusEntry() {&lt;br /&gt;
    var registry = sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    var pages = registry.pages &amp;amp;&amp;amp; typeof registry.pages === &#039;object&#039; ? registry.pages : registry;&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
    var title = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var keys = [&lt;br /&gt;
        normalized,&lt;br /&gt;
        raw,&lt;br /&gt;
        raw.replace(/_/g, &#039; &#039;),&lt;br /&gt;
        normalized.replace(/ /g, &#039;_&#039;),&lt;br /&gt;
        title,&lt;br /&gt;
        title.replace(/_/g, &#039; &#039;)&lt;br /&gt;
    ];&lt;br /&gt;
    var i;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; keys.length; i += 1) {&lt;br /&gt;
        if (keys[i] &amp;amp;&amp;amp; pages[keys[i]] &amp;amp;&amp;amp; typeof pages[keys[i]] === &#039;object&#039;) {&lt;br /&gt;
            return pages[keys[i]];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {};&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusOverride(lang) {&lt;br /&gt;
    var entry = getSidebarLanguageStatusEntry();&lt;br /&gt;
    return normalizeSidebarLanguageStatusValue(entry[lang]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function flushSidebarLanguageStatusCallbacks() {&lt;br /&gt;
    var callbacks = sidebarLanguageStatusCallbacks.slice();&lt;br /&gt;
    sidebarLanguageStatusCallbacks.length = 0;&lt;br /&gt;
&lt;br /&gt;
    callbacks.forEach(function(callback) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
            callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadSidebarLanguageStatusRegistry(callback, force) {&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        sidebarLanguageStatusCallbacks.push(callback);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoaded &amp;amp;&amp;amp; !force) {&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoading) return;&lt;br /&gt;
&lt;br /&gt;
    sidebarLanguageStatusLoading = true;&lt;br /&gt;
&lt;br /&gt;
    function finishLanguageStatus(parsed) {&lt;br /&gt;
        sidebarLanguageStatusRegistry = parsed &amp;amp;&amp;amp; typeof parsed === &#039;object&#039; ? parsed : {};&lt;br /&gt;
        sidebarLanguageStatusLoaded = true;&lt;br /&gt;
        sidebarLanguageStatusLoading = false;&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!force &amp;amp;&amp;amp; window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.fetchJsonRef === &#039;function&#039;) {&lt;br /&gt;
        window.EntryStore.fetchJsonRef(SIDEBAR_LANGUAGE_STATUS_TITLE, { noStore: false })&lt;br /&gt;
            .then(function (parsed) { finishLanguageStatus(parsed); })&lt;br /&gt;
            .catch(function () { finishLanguageStatus({}); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    (function () {&lt;br /&gt;
        var url = mw.util.getUrl(SIDEBAR_LANGUAGE_STATUS_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, SIDEBAR_LANGUAGE_STATUS_TITLE);&lt;br /&gt;
        }&lt;br /&gt;
        $.ajax({&lt;br /&gt;
            url: url,&lt;br /&gt;
            dataType: &#039;text&#039;,&lt;br /&gt;
            cache: true&lt;br /&gt;
        }).done(function(text) {&lt;br /&gt;
            var parsed = {};&lt;br /&gt;
&lt;br /&gt;
            try {&lt;br /&gt;
                parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                console.error(&#039;LanguageStatus.json parse failed:&#039;, err);&lt;br /&gt;
                parsed = {};&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            finishLanguageStatus(parsed);&lt;br /&gt;
        }).fail(function() {&lt;br /&gt;
            finishLanguageStatus({});&lt;br /&gt;
        });&lt;br /&gt;
    })();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.CLBI_LANGUAGE_STATUS = {&lt;br /&gt;
    title: SIDEBAR_LANGUAGE_STATUS_TITLE,&lt;br /&gt;
    languages: sidebarLanguageState.order.slice(),&lt;br /&gt;
    labels: SIDEBAR_LANGUAGE_LABELS,&lt;br /&gt;
    dialLabels: SIDEBAR_LANGUAGE_DIAL_LABELS,&lt;br /&gt;
    getPageKey: getSidebarLanguageStatusPageKey,&lt;br /&gt;
    getRegistry: function() {&lt;br /&gt;
        return sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    },&lt;br /&gt;
    reload: function(callback) {&lt;br /&gt;
        sidebarLanguageStatusLoaded = false;&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            renderSidebarLanguageBox();&lt;br /&gt;
            if (typeof callback === &#039;function&#039;) callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }, true);&lt;br /&gt;
    },&lt;br /&gt;
    refreshDial: function() {&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageMeta(lang) {&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var targetTitle = getLanguageTargetTitle(lang);&lt;br /&gt;
    var isCurrent = lang === currentLang;&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        lang: lang,&lt;br /&gt;
        code: getLangShortCode(lang),&lt;br /&gt;
        name: getSidebarLanguageName(lang),&lt;br /&gt;
        dialName: getSidebarLanguageDialName(lang),&lt;br /&gt;
        targetTitle: targetTitle,&lt;br /&gt;
        isCurrent: isCurrent,&lt;br /&gt;
        canMove: !!targetTitle &amp;amp;&amp;amp; !isCurrent&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatus(meta) {&lt;br /&gt;
    var override;&lt;br /&gt;
&lt;br /&gt;
    if (!meta) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (meta.isCurrent) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-current&#039;,&lt;br /&gt;
            label: &#039;CURRENT&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    override = getSidebarLanguageStatusOverride(meta.lang);&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;wip&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;WIP&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;unavailable&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;available&#039; || meta.targetTitle) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: meta.targetTitle ? &#039;is-ready&#039; : &#039;is-locked&#039;,&lt;br /&gt;
            label: meta.targetTitle ? &#039;AVAILABLE&#039; : &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: !!meta.targetTitle&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        className: &#039;is-locked&#039;,&lt;br /&gt;
        label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
        canApply: false&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguageRad(deg) {&lt;br /&gt;
    return (deg * Math.PI) / 180;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguagePointAt(radius, deg) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var angle = sidebarLanguageRad(deg);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        x: state.cx + Math.sin(angle) * radius,&lt;br /&gt;
        y: state.cy - Math.cos(angle) * radius&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageSectorPath(start, end) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var p1 = sidebarLanguagePointAt(state.outerR, start);&lt;br /&gt;
    var p2 = sidebarLanguagePointAt(state.outerR, end);&lt;br /&gt;
    var p3 = sidebarLanguagePointAt(state.innerR, end);&lt;br /&gt;
    var p4 = sidebarLanguagePointAt(state.innerR, start);&lt;br /&gt;
    var largeArc = Math.abs(end - start) &amp;gt; 180 ? 1 : 0;&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;M&#039;, p1.x.toFixed(3), p1.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.outerR, state.outerR, 0, largeArc, 1, p2.x.toFixed(3), p2.y.toFixed(3),&lt;br /&gt;
        &#039;L&#039;, p3.x.toFixed(3), p3.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.innerR, state.innerR, 0, largeArc, 0, p4.x.toFixed(3), p4.y.toFixed(3),&lt;br /&gt;
        &#039;Z&#039;&lt;br /&gt;
    ].join(&#039; &#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageShellPath() {&lt;br /&gt;
    return getSidebarLanguageSectorPath(-68, 68);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageByStep(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        index: index,&lt;br /&gt;
        meta: getSidebarLanguageMeta(state.order[index])&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewIndex() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    return normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewMeta() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    return getSidebarLanguageMeta(state.order[getSidebarLanguagePreviewIndex()]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeSidebarLanguageSector(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var item = getSidebarLanguageByStep(step);&lt;br /&gt;
    var group = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    var path = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    var label = createSidebarLanguageSvgEl(&#039;text&#039;);&lt;br /&gt;
    var labelY = state.cy - 78;&lt;br /&gt;
    var angle = step * state.sectorAngle;&lt;br /&gt;
&lt;br /&gt;
    group.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-group&#039;);&lt;br /&gt;
    group.setAttribute(&#039;data-step&#039;, String(step));&lt;br /&gt;
    group.setAttribute(&#039;data-index&#039;, String(item.index));&lt;br /&gt;
    group.setAttribute(&#039;data-lang&#039;, item.meta.lang);&lt;br /&gt;
    group.setAttribute(&#039;transform&#039;, &#039;rotate(&#039; + angle + &#039; &#039; + state.cx + &#039; &#039; + state.cy + &#039;)&#039;);&lt;br /&gt;
&lt;br /&gt;
    path.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector&#039;);&lt;br /&gt;
    path.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
&lt;br /&gt;
    label.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-label&#039;);&lt;br /&gt;
    label.setAttribute(&#039;x&#039;, String(state.cx));&lt;br /&gt;
    label.setAttribute(&#039;y&#039;, String(labelY + 5));&lt;br /&gt;
    label.textContent = item.meta.dialName || item.meta.name;&lt;br /&gt;
&lt;br /&gt;
    group.appendChild(path);&lt;br /&gt;
    group.appendChild(label);&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        if (sidebarLanguageState.dragging || performance.now() &amp;lt; sidebarLanguageState.suppressClickUntil) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        snapSidebarLanguageToStep(parseInt(group.getAttribute(&#039;data-step&#039;) || &#039;0&#039;, 10), true);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return group;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageWheel() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var svg;&lt;br /&gt;
    var defs;&lt;br /&gt;
    var clip;&lt;br /&gt;
    var clipPath;&lt;br /&gt;
    var shadowBlur;&lt;br /&gt;
    var blur;&lt;br /&gt;
    var fixedDepthGradient;&lt;br /&gt;
    var shell;&lt;br /&gt;
    var clipped;&lt;br /&gt;
    var rotor;&lt;br /&gt;
    var fixedDepthPath;&lt;br /&gt;
    var fixedFocus;&lt;br /&gt;
    var shadowSoft;&lt;br /&gt;
    var shadowHard;&lt;br /&gt;
    var rim;&lt;br /&gt;
    var pointer;&lt;br /&gt;
    var tri;&lt;br /&gt;
    var line;&lt;br /&gt;
    var step;&lt;br /&gt;
&lt;br /&gt;
    if (!fan) return;&lt;br /&gt;
&lt;br /&gt;
    fan.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    svg = createSidebarLanguageSvgEl(&#039;svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fan-svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 202 150&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;xMidYMid meet&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;role&#039;, &#039;img&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;aria-label&#039;, &#039;언어 선택 다이얼&#039;);&lt;br /&gt;
&lt;br /&gt;
    defs = createSidebarLanguageSvgEl(&#039;defs&#039;);&lt;br /&gt;
&lt;br /&gt;
    clip = createSidebarLanguageSvgEl(&#039;clipPath&#039;);&lt;br /&gt;
    clip.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fan-clip&#039;);&lt;br /&gt;
    clipPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    clipPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    clip.appendChild(clipPath);&lt;br /&gt;
&lt;br /&gt;
    shadowBlur = createSidebarLanguageSvgEl(&#039;filter&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-shadow-blur&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;x&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;y&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;width&#039;, &#039;140%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;height&#039;, &#039;140%&#039;);&lt;br /&gt;
    blur = createSidebarLanguageSvgEl(&#039;feGaussianBlur&#039;);&lt;br /&gt;
    blur.setAttribute(&#039;stdDeviation&#039;, &#039;3&#039;);&lt;br /&gt;
    shadowBlur.appendChild(blur);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthGradient = createSidebarLanguageSvgEl(&#039;linearGradient&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x2&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y2&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    [&lt;br /&gt;
        [&#039;0%&#039;, &#039;#ffffff&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;34%&#039;, &#039;#ffffff&#039;, &#039;0.006&#039;],&lt;br /&gt;
        [&#039;58%&#039;, &#039;#000000&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;100%&#039;, &#039;#000000&#039;, &#039;0.250&#039;]&lt;br /&gt;
    ].forEach(function(item) {&lt;br /&gt;
        var stop = createSidebarLanguageSvgEl(&#039;stop&#039;);&lt;br /&gt;
        stop.setAttribute(&#039;offset&#039;, item[0]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-color&#039;, item[1]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-opacity&#039;, item[2]);&lt;br /&gt;
        fixedDepthGradient.appendChild(stop);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    defs.appendChild(clip);&lt;br /&gt;
    defs.appendChild(shadowBlur);&lt;br /&gt;
    defs.appendChild(fixedDepthGradient);&lt;br /&gt;
    svg.appendChild(defs);&lt;br /&gt;
&lt;br /&gt;
    shell = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;class&#039;, &#039;sidebar-lang-shell&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shell);&lt;br /&gt;
&lt;br /&gt;
    clipped = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    clipped.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    rotor = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;class&#039;, &#039;sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
&lt;br /&gt;
    for (step = -state.repeats; step &amp;lt;= state.repeats; step += 1) {&lt;br /&gt;
        rotor.appendChild(makeSidebarLanguageSector(step));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    clipped.appendChild(rotor);&lt;br /&gt;
    svg.appendChild(clipped);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(fixedDepthPath);&lt;br /&gt;
&lt;br /&gt;
    fixedFocus = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-focus&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
    svg.appendChild(fixedFocus);&lt;br /&gt;
&lt;br /&gt;
    shadowSoft = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-soft&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowSoft);&lt;br /&gt;
&lt;br /&gt;
    shadowHard = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-hard&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowHard);&lt;br /&gt;
&lt;br /&gt;
    rim = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;class&#039;, &#039;sidebar-lang-rim&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(rim);&lt;br /&gt;
&lt;br /&gt;
    pointer = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-pointer&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    tri = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-triangle&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;d&#039;, &#039;M &#039; + (state.cx - 10) + &#039; 10 L &#039; + (state.cx + 10) + &#039; 10 L &#039; + state.cx + &#039; 26 Z&#039;);&lt;br /&gt;
    pointer.appendChild(tri);&lt;br /&gt;
&lt;br /&gt;
    line = createSidebarLanguageSvgEl(&#039;line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;x1&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;x2&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;y1&#039;, &#039;24&#039;);&lt;br /&gt;
    line.setAttribute(&#039;y2&#039;, &#039;112&#039;);&lt;br /&gt;
    pointer.appendChild(line);&lt;br /&gt;
&lt;br /&gt;
    svg.appendChild(pointer);&lt;br /&gt;
    fan.appendChild(svg);&lt;br /&gt;
&lt;br /&gt;
    state.rotor = rotor;&lt;br /&gt;
    setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateSidebarLanguageDial() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var meta = getSidebarLanguagePreviewMeta();&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
    var selectedValue = document.getElementById(&#039;clbi-sidebar-lang-selected-value&#039;);&lt;br /&gt;
    var availabilityPanel = document.getElementById(&#039;clbi-sidebar-lang-availability-panel&#039;);&lt;br /&gt;
    var availabilityValue = document.getElementById(&#039;clbi-sidebar-lang-availability-value&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (selectedValue) {&lt;br /&gt;
        selectedValue.textContent = meta.name;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityPanel) {&lt;br /&gt;
        availabilityPanel.classList.remove(&#039;is-ready&#039;, &#039;is-current&#039;, &#039;is-locked&#039;);&lt;br /&gt;
        availabilityPanel.classList.add(status.className);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityValue) {&lt;br /&gt;
        availabilityValue.textContent = status.label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (apply) {&lt;br /&gt;
        apply.classList.toggle(&#039;is-disabled&#039;, !status.canApply);&lt;br /&gt;
        apply.setAttribute(&#039;aria-disabled&#039;, status.canApply ? &#039;false&#039; : &#039;true&#039;);&lt;br /&gt;
        apply.setAttribute(&#039;aria-label&#039;, status.canApply ? (meta.name + &#039; 적용&#039;) : (meta.isCurrent ? &#039;현재 언어&#039; : &#039;사용할 수 없는 언어&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (selector) {&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-lang&#039;, meta.lang);&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-code&#039;, meta.code);&lt;br /&gt;
        selector.classList.toggle(&#039;is-current&#039;, meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-ready&#039;, status.canApply);&lt;br /&gt;
        selector.classList.toggle(&#039;is-locked&#039;, !status.canApply &amp;amp;&amp;amp; !meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-dragging&#039;, !!state.dragging);&lt;br /&gt;
        selector.classList.toggle(&#039;is-spinning&#039;, !!state.inertiaRaf);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        meta: meta,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageRotation(value, animate) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.rotation = value;&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (!state.rotor) return;&lt;br /&gt;
&lt;br /&gt;
    if (animate) {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-snapping&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.rotor.style.transform = &#039;rotate(&#039; + state.rotation.toFixed(3) + &#039;deg)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestSidebarLanguageRotation(value) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.pendingRotation = value;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) return;&lt;br /&gt;
&lt;br /&gt;
    state.raf = requestAnimationFrame(function() {&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
        setSidebarLanguageRotation(state.pendingRotation, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function cancelSidebarLanguageSpin() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    if (state.inertiaRaf) {&lt;br /&gt;
        cancelAnimationFrame(state.inertiaRaf);&lt;br /&gt;
        state.inertiaRaf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finishSidebarLanguageSnap(nearestIndex, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    state.baseIndex = normalizeSidebarLanguageIndex(nearestIndex);&lt;br /&gt;
    state.selectedIndex = state.baseIndex;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping is-dragging is-spinning&#039;);&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        callback(getSidebarLanguageMeta(state.order[state.selectedIndex]));&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageToStep(step, animate, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var targetRotation = -step * state.sectorAngle;&lt;br /&gt;
    var nearestIndex = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.selectedIndex = nearestIndex;&lt;br /&gt;
    setSidebarLanguageRotation(targetRotation, !!animate);&lt;br /&gt;
&lt;br /&gt;
    state.snapTimer = setTimeout(function() {&lt;br /&gt;
        finishSidebarLanguageSnap(nearestIndex, callback);&lt;br /&gt;
    }, animate ? 230 : 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageNearest(callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    snapSidebarLanguageToStep(step, true, callback);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function startSidebarLanguageInertiaSpin(initialVelocity) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var velocity;&lt;br /&gt;
    var lastFrame;&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
&lt;br /&gt;
    velocity = Math.max(-state.maxSpinVelocity, Math.min(state.maxSpinVelocity, initialVelocity));&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(velocity) &amp;lt; state.minSpinVelocity) {&lt;br /&gt;
        snapSidebarLanguageNearest();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-spinning&#039;);&lt;br /&gt;
    lastFrame = performance.now();&lt;br /&gt;
&lt;br /&gt;
    function frame(now) {&lt;br /&gt;
        var dt = Math.min(34, Math.max(1, now - lastFrame));&lt;br /&gt;
        var sign = velocity &amp;lt; 0 ? -1 : 1;&lt;br /&gt;
        var nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        state.rotation += velocity * dt;&lt;br /&gt;
        setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
&lt;br /&gt;
        nextSpeed = Math.max(0, Math.abs(velocity) - (state.spinDecel * dt));&lt;br /&gt;
        velocity = sign * nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        if (nextSpeed &amp;lt;= state.minSpinVelocity) {&lt;br /&gt;
            state.inertiaRaf = null;&lt;br /&gt;
            $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleSidebarLanguageNavigation(meta) {&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
&lt;br /&gt;
    if (!meta || !status.canApply) return;&lt;br /&gt;
&lt;br /&gt;
    clearTimeout(sidebarLanguageState.navigateTimer);&lt;br /&gt;
    sidebarLanguageState.navigateTimer = setTimeout(function() {&lt;br /&gt;
        var title = getLanguageTargetTitle(meta.lang);&lt;br /&gt;
&lt;br /&gt;
        if (!title || meta.lang === getCurrentLang()) return;&lt;br /&gt;
&lt;br /&gt;
        window.location.href = buildWikiPath(title);&lt;br /&gt;
    }, 70);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageSelection(lang) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = state.order.indexOf(lang);&lt;br /&gt;
&lt;br /&gt;
    if (index &amp;lt; 0) index = state.order.indexOf(getCurrentLang());&lt;br /&gt;
    if (index &amp;lt; 0) index = 0;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) {&lt;br /&gt;
        cancelAnimationFrame(state.raf);&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.currentLang = lang;&lt;br /&gt;
    state.baseIndex = index;&lt;br /&gt;
    state.selectedIndex = index;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
    state.dragMoved = false;&lt;br /&gt;
    state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveSidebarLanguageSelection(delta) {&lt;br /&gt;
    snapSidebarLanguageToStep(-delta, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindSidebarLanguageSelector() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!selector || !fan || !apply) return;&lt;br /&gt;
&lt;br /&gt;
    if (state.bound &amp;amp;&amp;amp; state.boundElement === selector) return;&lt;br /&gt;
&lt;br /&gt;
    state.bound = true;&lt;br /&gt;
    state.boundElement = selector;&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerdown&#039;, function(e) {&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
        state.dragging = true;&lt;br /&gt;
        state.dragMoved = false;&lt;br /&gt;
        state.dragStartX = e.clientX;&lt;br /&gt;
        state.dragStartY = e.clientY || 0;&lt;br /&gt;
        state.dragStartRotation = state.rotation;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = performance.now();&lt;br /&gt;
        state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
        selector.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
        selector.classList.remove(&#039;is-snapping&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        Vertical page scrolling must stay available when the pointer starts on&lt;br /&gt;
        the language dial. Capture and preventDefault are delayed until a&lt;br /&gt;
        horizontal drag is confirmed.&lt;br /&gt;
        */&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointermove&#039;, function(e) {&lt;br /&gt;
        var now;&lt;br /&gt;
        var totalDx;&lt;br /&gt;
        var totalDy;&lt;br /&gt;
        var frameDx;&lt;br /&gt;
        var dt;&lt;br /&gt;
        var instantVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        totalDx = e.clientX - state.dragStartX;&lt;br /&gt;
        totalDy = (e.clientY || 0) - state.dragStartY;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragAxis &amp;amp;&amp;amp; (Math.abs(totalDx) &amp;gt; 4 || Math.abs(totalDy) &amp;gt; 4)) {&lt;br /&gt;
            state.dragAxis = Math.abs(totalDx) &amp;gt;= Math.abs(totalDy) ? &#039;x&#039; : &#039;y&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (state.dragAxis === &#039;y&#039;) {&lt;br /&gt;
                state.dragging = false;&lt;br /&gt;
                state.dragMoved = false;&lt;br /&gt;
                state.dragAxis = null;&lt;br /&gt;
                state.pointerCaptured = false;&lt;br /&gt;
                selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (fan.setPointerCapture &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
                try {&lt;br /&gt;
                    fan.setPointerCapture(e.pointerId);&lt;br /&gt;
                    state.pointerCaptured = true;&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    state.pointerCaptured = false;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragAxis !== &#039;x&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        now = performance.now();&lt;br /&gt;
        frameDx = e.clientX - state.lastX;&lt;br /&gt;
        dt = Math.max(1, now - state.lastTime);&lt;br /&gt;
&lt;br /&gt;
        if (Math.abs(totalDx) &amp;gt; 3) state.dragMoved = true;&lt;br /&gt;
&lt;br /&gt;
        instantVelocity = (frameDx * state.dragSensitivity) / dt;&lt;br /&gt;
        state.releaseVelocity = (state.releaseVelocity * 0.62) + (instantVelocity * 0.38);&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = now;&lt;br /&gt;
&lt;br /&gt;
        requestSidebarLanguageRotation(state.dragStartRotation + totalDx * state.dragSensitivity);&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function finishDrag(e) {&lt;br /&gt;
        var velocityAge;&lt;br /&gt;
        var throwVelocity;&lt;br /&gt;
        var wasHorizontal;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        wasHorizontal = state.dragAxis === &#039;x&#039;;&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (fan.releasePointerCapture &amp;amp;&amp;amp; state.pointerCaptured &amp;amp;&amp;amp; e &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
            try { fan.releasePointerCapture(e.pointerId); } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
&lt;br /&gt;
        if (!wasHorizontal &amp;amp;&amp;amp; !state.dragMoved) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        velocityAge = performance.now() - state.lastTime;&lt;br /&gt;
        throwVelocity = velocityAge &amp;gt; 120 ? 0 : state.releaseVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(throwVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            startSidebarLanguageInertiaSpin(throwVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerup&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;pointercancel&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;lostpointercapture&#039;, function() {&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(state.releaseVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
            startSidebarLanguageInertiaSpin(state.releaseVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    apply.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        snapSidebarLanguageNearest(function(meta) {&lt;br /&gt;
            scheduleSidebarLanguageNavigation(meta);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    selector.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
        if (e.key === &#039;ArrowLeft&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(-1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;ArrowRight&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;Enter&#039; || e.key === &#039; &#039;) {&lt;br /&gt;
            apply.click();&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageBox() {&lt;br /&gt;
    bindSidebarLanguageSelector();&lt;br /&gt;
    setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
&lt;br /&gt;
    if (!sidebarLanguageStatusLoaded) {&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadRecentChangesList(targetSelector, limit) {&lt;br /&gt;
    var $target = $(targetSelector);&lt;br /&gt;
&lt;br /&gt;
    if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
    var isNewsList = $target.closest(&#039;.clbi-left-news-box&#039;).length &amp;gt; 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.html((t &amp;amp;&amp;amp; t.loading) ? t.loading : &#039;불러오는 중...&#039;);&lt;br /&gt;
&lt;br /&gt;
    $.getJSON(&lt;br /&gt;
        &#039;/api.php?action=query&amp;amp;list=recentchanges&amp;amp;rclimit=&#039; + encodeURIComponent(limit || 5) + &#039;&amp;amp;rcprop=title|timestamp|user&amp;amp;format=json&amp;amp;rcnamespace=0&amp;amp;rctype=edit|new&#039;,&lt;br /&gt;
        function(data) {&lt;br /&gt;
            var items = data &amp;amp;&amp;amp; data.query ? data.query.recentchanges : [];&lt;br /&gt;
            var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (!items || !items.length) {&lt;br /&gt;
                $target.html(&#039;표시할 변경 사항이 없습니다.&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $.each(items, function(i, item) {&lt;br /&gt;
                var label = timeAgo(item.timestamp);&lt;br /&gt;
                var title = item.title || &#039;&#039;;&lt;br /&gt;
                var userName = item.user || &#039;Unknown&#039;;&lt;br /&gt;
                var pageHref = buildWikiPath(title);&lt;br /&gt;
                var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(userName) + &#039;.png&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (isNewsList) {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;news-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;news-recent-avatar&amp;quot; src=&amp;quot;&#039; + escapeHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;news-recent-main&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-meta&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-user&amp;quot;&amp;gt;@&#039; + escapeHtml(userName) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
                } else {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;clbi-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;clbi-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList) {&lt;br /&gt;
                $target.html(&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-recent-viewport&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-recent-stack&amp;quot;&amp;gt;&#039; + html + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
                    ensureNewsBottomFinish();&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                $target.html(html);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList &amp;amp;&amp;amp; typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
                scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $target.find(isNewsList ? &#039;.news-recent-item&#039; : &#039;.clbi-recent-item&#039;).each(function() {&lt;br /&gt;
                var wrap = $(this).find(isNewsList ? &#039;.news-recent-title-wrap&#039; : &#039;.clbi-recent-title-wrap&#039;);&lt;br /&gt;
                var title = $(this).find(isNewsList ? &#039;.news-recent-title&#039; : &#039;.clbi-recent-title&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (!wrap.length || !title.length) return;&lt;br /&gt;
&lt;br /&gt;
                var wrapW = wrap.width();&lt;br /&gt;
                var titleW = title[0].scrollWidth;&lt;br /&gt;
&lt;br /&gt;
                if (titleW &amp;gt; wrapW + 20) {&lt;br /&gt;
                    var duration = titleW / 40;&lt;br /&gt;
&lt;br /&gt;
                    title.css({&lt;br /&gt;
                        animation: &#039;clbi-scroll &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
                        &#039;--scroll-dist&#039;: &#039;-&#039; + (titleW - wrapW + 8) + &#039;px&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    ).fail(function() {&lt;br /&gt;
        var lang = getCurrentLang();&lt;br /&gt;
        var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
&lt;br /&gt;
        $target.html((t &amp;amp;&amp;amp; t.loadFail) ? t.loadFail : &#039;불러오기 실패&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureRecentViewport() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var viewport;&lt;br /&gt;
    var stack;&lt;br /&gt;
    var children;&lt;br /&gt;
&lt;br /&gt;
    if (!list) return null;&lt;br /&gt;
&lt;br /&gt;
    viewport = list.querySelector(&#039;:scope &amp;gt; .news-recent-viewport&#039;);&lt;br /&gt;
    stack = viewport ? viewport.querySelector(&#039;:scope &amp;gt; .news-recent-stack&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (viewport &amp;amp;&amp;amp; stack) return viewport;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(list.children || []);&lt;br /&gt;
&lt;br /&gt;
    viewport = document.createElement(&#039;div&#039;);&lt;br /&gt;
    viewport.className = &#039;news-recent-viewport&#039;;&lt;br /&gt;
&lt;br /&gt;
    stack = document.createElement(&#039;div&#039;);&lt;br /&gt;
    stack.className = &#039;news-recent-stack&#039;;&lt;br /&gt;
&lt;br /&gt;
    children.forEach(function (child) {&lt;br /&gt;
        if (child.classList &amp;amp;&amp;amp; child.classList.contains(&#039;news-recent-viewport&#039;)) return;&lt;br /&gt;
        stack.appendChild(child);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    viewport.appendChild(stack);&lt;br /&gt;
    list.appendChild(viewport);&lt;br /&gt;
&lt;br /&gt;
    return viewport;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ensureNewsBottomFinish() {&lt;br /&gt;
    var newsBox = document.querySelector(&#039;#clbi-left-sidebar .clbi-left-news-box&#039;);&lt;br /&gt;
    var content = newsBox ? newsBox.querySelector(&#039;.clbi-news-box&#039;) : null;&lt;br /&gt;
    var finish;&lt;br /&gt;
&lt;br /&gt;
    if (!content) return null;&lt;br /&gt;
&lt;br /&gt;
    finish = content.querySelector(&#039;:scope &amp;gt; .news-bottom-finish&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!finish) {&lt;br /&gt;
        finish = document.createElement(&#039;div&#039;);&lt;br /&gt;
        finish.className = &#039;news-bottom-finish&#039;;&lt;br /&gt;
        finish.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        content.appendChild(finish);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return finish;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateAdaptiveLeftRecentItems() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: 좌측 사이드는 뉴스 확장형 flex 레이아웃이 높이를 담당한다.&lt;br /&gt;
    이전 adaptive 코드는 항목을 숨기거나 mask/fade를 걸기 위한 것이었으므로&lt;br /&gt;
    여기서는 잔여 상태만 정리하고 DOM 래퍼만 보장한다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftRecentAdaptiveState();&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureRecentViewport === &#039;function&#039;) {&lt;br /&gt;
        ensureRecentViewport();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
        ensureNewsBottomFinish();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleAdaptiveLeftRecentItems() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateAdaptiveLeftRecentItems();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 80);&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateClbiContentBottomGap(iteration) {&lt;br /&gt;
    var content = document.querySelector(&#039;.container-fluid.liberty-content&#039;);&lt;br /&gt;
    var main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var desiredGap = 8;&lt;br /&gt;
    var rootStyle;&lt;br /&gt;
    var scale = 1;&lt;br /&gt;
    var contentRect;&lt;br /&gt;
    var bottomTop;&lt;br /&gt;
    var targetHeight;&lt;br /&gt;
    var currentHeight;&lt;br /&gt;
    var visualGap;&lt;br /&gt;
&lt;br /&gt;
    iteration = iteration || 0;&lt;br /&gt;
&lt;br /&gt;
    if (!content || !main || !bottomNav) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    하단 간격은 scale 모드에서도 같은 기준으로 계산한다.&lt;br /&gt;
    transform:scale()이 걸리면 getBoundingClientRect()는 축소된 화면 좌표를 반환하므로,&lt;br /&gt;
    목표 간격 8px도 scale을 곱한 화면 좌표로 비교하고 다시 design px로 환산한다.&lt;br /&gt;
&lt;br /&gt;
    목표:&lt;br /&gt;
    .liberty-content-main.bottom === #clbi-bottom-nav-wrap.top - 8px&lt;br /&gt;
    */&lt;br /&gt;
    if (document.body &amp;amp;&amp;amp; document.body.classList &amp;amp;&amp;amp; document.body.classList.contains(&#039;clbi-shell-vertical-scale&#039;)) {&lt;br /&gt;
        rootStyle = window.getComputedStyle(document.documentElement);&lt;br /&gt;
        scale = parseFloat(rootStyle.getPropertyValue(&#039;--clbi-shell-scale&#039;)) || 1;&lt;br /&gt;
        scale = Math.max(0.25, scale);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    contentRect = content.getBoundingClientRect();&lt;br /&gt;
    bottomTop = bottomNav.getBoundingClientRect().top;&lt;br /&gt;
    visualGap = desiredGap * scale;&lt;br /&gt;
    targetHeight = Math.floor((bottomTop - contentRect.top - visualGap) / scale);&lt;br /&gt;
    targetHeight = Math.max(120, targetHeight);&lt;br /&gt;
&lt;br /&gt;
    currentHeight = Math.round(content.getBoundingClientRect().height / scale);&lt;br /&gt;
&lt;br /&gt;
    content.style.setProperty(&#039;--clbi-content-extra&#039;, &#039;0px&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;max-height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(currentHeight - targetHeight) &amp;gt;= 1 &amp;amp;&amp;amp; iteration &amp;lt; 4) {&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            updateClbiContentBottomGap(iteration + 1);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiContentBottomGap() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 40);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 120);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 280);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 520);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateLeftBillboardAdaptive() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: Ad는 이미지/CRT 비율을 유지하는 고정 슬롯이다.&lt;br /&gt;
    남는 세로 공간은 뉴스 박스가 흡수하므로, Ad에 하단 finish를 늘리거나&lt;br /&gt;
    title-only 상태로 접는 adaptive 보정은 사용하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftBillboardAdaptiveState();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftBillboardAdaptive() {&lt;br /&gt;
    window.requestAnimationFrame(updateLeftBillboardAdaptive);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 80);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftSidebarVerticalFit() {&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
            scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
            scheduleLeftBillboardAdaptive();&lt;br /&gt;
        }&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시대 문서 전용 왼쪽 사이드바 이미지&lt;br /&gt;
function updateLeftSidebarNationsImage() {&lt;br /&gt;
    $(&#039;#clbi-left-nations-image&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setProfileActionLabel(selector, text) {&lt;br /&gt;
    var target = $(selector);&lt;br /&gt;
    var label = target.find(&#039;.profile-action-label&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (label.length) {&lt;br /&gt;
        label.text(text);&lt;br /&gt;
    } else {&lt;br /&gt;
        target.text(text);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 사이드바 업데이트&lt;br /&gt;
function updateSidebar() {&lt;br /&gt;
    if (!window.LANG) {&lt;br /&gt;
        setTimeout(updateSidebar, 100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
&lt;br /&gt;
    var newsTitle = t.news || &#039;뉴스&#039;;&lt;br /&gt;
    var changelogTitle = t.changelog || &#039;체인지로그&#039;;&lt;br /&gt;
    var recentTitle = t.recentChanges || &#039;최근 변경&#039;;&lt;br /&gt;
    var languageTitle = t.language || &#039;언어&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-language&#039;).text(languageTitle);&lt;br /&gt;
    renderSidebarLanguageBox();&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-news&#039;).text(newsTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-recent-main&#039;).text(recentTitle);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-search a&#039;).text(t.search);&lt;br /&gt;
    $(&#039;#clbi-search-input&#039;).attr(&#039;placeholder&#039;, t.search + &#039;...&#039;);&lt;br /&gt;
    $(&#039;#clbi-title-recent a&#039;).text(recentTitle);&lt;br /&gt;
    $(&#039;#clbi-title-guide-label&#039;).text(t.guide);&lt;br /&gt;
    $(&#039;#clbi-guide-link&#039;).text(t.getStarted);&lt;br /&gt;
    $(&#039;#clbi-title-links-label&#039;).text(t.links);&lt;br /&gt;
&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-contribution&#039;, t.contribution);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-watchlist&#039;, t.watchlist);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-preferences&#039;, t.preferences);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-logout&#039;, t.logout);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-login&#039;, t.login);&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var specialPage = String(mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
$(&#039;#clbi-left-news-recent-title&#039;).text(&#039;RECENT CHANGES&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.clbi-user-btn&#039;).removeClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;Contributions&#039; ||&lt;br /&gt;
        specialPage === &#039;기여&#039; ||&lt;br /&gt;
        pageName.indexOf(&#039;특수:기여&#039;) === 0 ||&lt;br /&gt;
        pageName.indexOf(&#039;Special:Contributions&#039;) === 0&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-contribution&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (specialPage === &#039;Watchlist&#039;) {&lt;br /&gt;
        $(&#039;#clbi-btn-watchlist&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;설정&#039; ||&lt;br /&gt;
        pageName === &#039;특수:설정&#039; ||&lt;br /&gt;
        pageName === &#039;Special:설정&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-preferences&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.toggleBtn&#039;).each(function() {&lt;br /&gt;
        var btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        if (!$(&#039;#&#039; + btn.data(&#039;target&#039;)).hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            btn.text(t.expand);&lt;br /&gt;
        } else {&lt;br /&gt;
            btn.text(t.collapse);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    updateLeftSidebarNationsImage();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function canShowContentTools() {&lt;br /&gt;
    // 비로그인 사용자는 편집/역사/공유 버튼을 숨김&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // MediaWiki가 현재 문서를 편집 가능하지 않다고 판단하면 숨김&lt;br /&gt;
    var isEditable = mw.config.get(&#039;wgIsProbablyEditable&#039;);&lt;br /&gt;
    if (isEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var relevantEditable = mw.config.get(&#039;wgRelevantPageIsProbablyEditable&#039;);&lt;br /&gt;
    if (relevantEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkNodes(root) {&lt;br /&gt;
    var seen = [];&lt;br /&gt;
    var nodes = [];&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
&lt;br /&gt;
    $root.find(&#039;#catlinks, .catlinks&#039;).add($root.filter(&#039;#catlinks, .catlinks&#039;)).each(function () {&lt;br /&gt;
        if (seen.indexOf(this) !== -1) return;&lt;br /&gt;
        seen.push(this);&lt;br /&gt;
        nodes.push(this);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return nodes;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinksTarget(root) {&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
    var parserOutput = $root.find(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
    var main = $root.find(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.liberty-content-main&#039;)) {&lt;br /&gt;
        parserOutput = $(root).find(&#039;.mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.mw-parser-output&#039;)) {&lt;br /&gt;
        parserOutput = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (parserOutput.length) return parserOutput;&lt;br /&gt;
    if (main.length) return main;&lt;br /&gt;
&lt;br /&gt;
    if (!root) {&lt;br /&gt;
        parserOutput = $(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
        if (parserOutput.length) return parserOutput;&lt;br /&gt;
        if (main.length) return main;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return $();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_CATLINKS_FETCH_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function getCurrentPageTitleForCatlinks() {&lt;br /&gt;
    return String(&lt;br /&gt;
        mw.config.get(&#039;wgPageName&#039;) ||&lt;br /&gt;
        mw.config.get(&#039;wgRelevantPageName&#039;) ||&lt;br /&gt;
        &#039;&#039;&lt;br /&gt;
    ).trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function shouldFetchCatlinks() {&lt;br /&gt;
    var pageName = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) return false;&lt;br /&gt;
    if (specialPage) return false;&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearCatlinksInlineHiding(cat) {&lt;br /&gt;
    if (!cat || !cat.style) return;&lt;br /&gt;
&lt;br /&gt;
    cat.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-normal-catlinks, #mw-normal-catlinks, .mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        if (!this.style) return;&lt;br /&gt;
        this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function exposeHiddenCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        this.classList.remove(&#039;mw-hidden-cats-hidden&#039;);&lt;br /&gt;
        this.classList.add(&#039;mw-hidden-cats-user-shown&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (this.style) {&lt;br /&gt;
            this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkTextContent(cat) {&lt;br /&gt;
    var clone;&lt;br /&gt;
    var text;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    clone = cat.cloneNode(true);&lt;br /&gt;
    $(clone).find(&#039;script, style&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    text = String(clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*:\s*/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*$/i, &#039;&#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function hasRenderedCatlinkItems(cat) {&lt;br /&gt;
    var hasLink;&lt;br /&gt;
    var hasListText;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    hasLink = false;&lt;br /&gt;
    $(cat).find(&#039;a&#039;).each(function () {&lt;br /&gt;
        var text = String($(this).text() || &#039;&#039;).trim();&lt;br /&gt;
        var href = String(this.getAttribute(&#039;href&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        if (text || href) hasLink = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasLink) return true;&lt;br /&gt;
&lt;br /&gt;
    hasListText = false;&lt;br /&gt;
    $(cat).find(&#039;li&#039;).each(function () {&lt;br /&gt;
        if (String($(this).text() || &#039;&#039;).trim()) hasListText = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasListText) return true;&lt;br /&gt;
&lt;br /&gt;
    return !!getCatlinkTextContent(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCategoryTitle(rawTitle) {&lt;br /&gt;
    var title = String(rawTitle == null ? &#039;&#039; : rawTitle).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!title) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    title = title.replace(/_/g, &#039; &#039;);&lt;br /&gt;
&lt;br /&gt;
    if (/^(Category|분류):/i.test(title)) {&lt;br /&gt;
        return title;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;분류:&#039; + title;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeCategoryLinkTitle(rawTitle) {&lt;br /&gt;
    return String(rawTitle || &#039;&#039;)&lt;br /&gt;
        .replace(/^Category:/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^분류:/, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function dedupeCatlinkCategories(categories) {&lt;br /&gt;
    var seen = {};&lt;br /&gt;
    var result = [];&lt;br /&gt;
&lt;br /&gt;
    (categories || []).forEach(function (item) {&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var hidden = false;&lt;br /&gt;
&lt;br /&gt;
        if (typeof item === &#039;string&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item);&lt;br /&gt;
        } else if (item &amp;amp;&amp;amp; typeof item === &#039;object&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item.title || item.name || item.category || &#039;&#039;);&lt;br /&gt;
            hidden = item.hidden !== undefined || item.isHidden === true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
        if (seen[title]) return;&lt;br /&gt;
&lt;br /&gt;
        seen[title] = true;&lt;br /&gt;
        result.push({ title: title, hidden: hidden });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getConfigCatlinksCategories() {&lt;br /&gt;
    var normal = mw.config.get(&#039;wgCategories&#039;) || [];&lt;br /&gt;
    var hidden = mw.config.get(&#039;wgHiddenCategories&#039;) || [];&lt;br /&gt;
    var categories = [];&lt;br /&gt;
&lt;br /&gt;
    if (!Array.isArray(normal)) normal = [];&lt;br /&gt;
    if (!Array.isArray(hidden)) hidden = [];&lt;br /&gt;
&lt;br /&gt;
    normal.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: false });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    hidden.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: true });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return dedupeCatlinkCategories(categories);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksReady(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksEmpty(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksPending(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-fetching&#039;, &#039;1&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderFetchedCatlinks(cat, categories, pageTitle) {&lt;br /&gt;
    var container;&lt;br /&gt;
    var ul;&lt;br /&gt;
    var normalized;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    normalized = dedupeCatlinkCategories(categories);&lt;br /&gt;
&lt;br /&gt;
    if (!normalized.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cat.innerHTML = &#039;&#039;;&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-api-populated&#039;);&lt;br /&gt;
&lt;br /&gt;
    container = document.createElement(&#039;div&#039;);&lt;br /&gt;
    container.className = &#039;mw-normal-catlinks&#039;;&lt;br /&gt;
    container.appendChild(document.createTextNode(&#039;분류: &#039;));&lt;br /&gt;
&lt;br /&gt;
    ul = document.createElement(&#039;ul&#039;);&lt;br /&gt;
&lt;br /&gt;
    normalized.forEach(function (item) {&lt;br /&gt;
        var title = String(item &amp;amp;&amp;amp; item.title ? item.title : &#039;&#039;).trim();&lt;br /&gt;
        var li;&lt;br /&gt;
        var a;&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
&lt;br /&gt;
        li = document.createElement(&#039;li&#039;);&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = mw.util.getUrl(title);&lt;br /&gt;
        a.title = title;&lt;br /&gt;
        a.textContent = makeCategoryLinkTitle(title);&lt;br /&gt;
&lt;br /&gt;
        if (item.hidden) {&lt;br /&gt;
            li.className = &#039;clbi-hidden-category-item&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        li.appendChild(a);&lt;br /&gt;
        ul.appendChild(li);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (!ul.children.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.appendChild(ul);&lt;br /&gt;
    cat.appendChild(container);&lt;br /&gt;
    markCatlinksReady(cat, pageTitle);&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksForPage(pageTitle, callback) {&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (typeof pageTitle === &#039;function&#039;) {&lt;br /&gt;
        callback = pageTitle;&lt;br /&gt;
        pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    pageTitle = String(pageTitle || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!pageTitle || !shouldFetchCatlinks()) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;categories&#039;,&lt;br /&gt;
        titles: pageTitle,&lt;br /&gt;
        cllimit: &#039;max&#039;,&lt;br /&gt;
        clprop: &#039;hidden&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).done(function (data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages &amp;amp;&amp;amp; pages.length ? pages[0] : null;&lt;br /&gt;
        var categories = page &amp;amp;&amp;amp; page.categories ? page.categories : [];&lt;br /&gt;
&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback(categories || [], pageTitle);&lt;br /&gt;
    }).fail(function () {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finalizeEmptyCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    markCatlinksEmpty(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksIfNeeded(cat) {&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(cat, configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) {&lt;br /&gt;
        finalizeEmptyCatlinks(cat);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (cat.getAttribute(&#039;data-clbi-catlinks-fetching&#039;) === &#039;1&#039; &amp;amp;&amp;amp; cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) === pageTitle) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
    markCatlinksPending(cat, pageTitle);&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (!cat || !cat.isConnected) return;&lt;br /&gt;
        if (cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) !== requestedPage) return;&lt;br /&gt;
&lt;br /&gt;
        if (!renderFetchedCatlinks(cat, categories, requestedPage)) {&lt;br /&gt;
            finalizeEmptyCatlinks(cat);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCatlinksPanel(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksIfNeeded(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createCatlinksPanel(target, className) {&lt;br /&gt;
    var cat;&lt;br /&gt;
&lt;br /&gt;
    if (!target || !target.length) return null;&lt;br /&gt;
&lt;br /&gt;
    cat = document.createElement(&#039;div&#039;);&lt;br /&gt;
    cat.id = &#039;catlinks&#039;;&lt;br /&gt;
    cat.className = className || &#039;catlinks clbi-catlinks-created clbi-catlinks-pending&#039;;&lt;br /&gt;
    target.append(cat);&lt;br /&gt;
    return cat;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prepareSpaCatlinksBeforeInsert(root) {&lt;br /&gt;
    var nodes;&lt;br /&gt;
    var target;&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    nodes = getCatlinkNodes(root);&lt;br /&gt;
&lt;br /&gt;
    nodes.forEach(function (node) {&lt;br /&gt;
        node.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
        clearCatlinksInlineHiding(node);&lt;br /&gt;
        exposeHiddenCatlinks(node);&lt;br /&gt;
&lt;br /&gt;
        if (hasRenderedCatlinkItems(node)) {&lt;br /&gt;
            markCatlinksReady(node, pageTitle);&lt;br /&gt;
        } else if (configCategories.length) {&lt;br /&gt;
            renderFetchedCatlinks(node, configCategories, pageTitle);&lt;br /&gt;
        } else {&lt;br /&gt;
            markCatlinksEmpty(node);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (nodes.length || !configCategories.length) return;&lt;br /&gt;
&lt;br /&gt;
    target = getCatlinksTarget(root);&lt;br /&gt;
    if (!target.length) target = $(root);&lt;br /&gt;
&lt;br /&gt;
    renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveCatlinksToBottom() {&lt;br /&gt;
    var main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
    var target = getCatlinksTarget();&lt;br /&gt;
    var catlinks = getCatlinkNodes();&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!main.length || !target.length) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    catlinks.forEach(function (node) {&lt;br /&gt;
        var catNode = $(node);&lt;br /&gt;
&lt;br /&gt;
        if (node.parentNode !== target[0]) {&lt;br /&gt;
            catNode.appendTo(target);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        normalizeCatlinksPanel(node);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (catlinks.length) return;&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        var cat;&lt;br /&gt;
&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (getCatlinkNodes().length) return;&lt;br /&gt;
        if (!categories || !categories.length) return;&lt;br /&gt;
&lt;br /&gt;
        cat = createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;);&lt;br /&gt;
        renderFetchedCatlinks(cat, categories, requestedPage);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 대문 스타일&lt;br /&gt;
function initCategoryNavIfAvailable(root) {&lt;br /&gt;
    /*&lt;br /&gt;
    CategoryNav.js는 대문 카테고리 네비를 SVG로 생성한다.&lt;br /&gt;
&lt;br /&gt;
    Common.js가 SPA로 본문을 갈아끼운 뒤에는 MediaWiki 원래 페이지 로드와 달리&lt;br /&gt;
    CategoryNav.js의 초기 DOMContentLoaded만으로는 새 mount를 다시 잡지 못할 수 있다.&lt;br /&gt;
    CategoryNav.js 자체도 mw.hook(&#039;wikipage.content&#039;)를 듣지만, 로드 순서와 SPA 타이밍이&lt;br /&gt;
    엇갈릴 수 있으므로 Common.js 쪽에서도 존재 여부를 확인한 뒤 한 번 더 호출한다.&lt;br /&gt;
&lt;br /&gt;
    이 함수는 CategoryNav.js가 아직 로드되지 않았으면 아무 것도 하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    if (&lt;br /&gt;
        window.CLBI &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI.categoryNav &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI.categoryNav.init === &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        window.CLBI.categoryNav.init(root || document);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeLegacyMainPageHero() {&lt;br /&gt;
    /*&lt;br /&gt;
    기존 대문 전용 레거시 요소 정리&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    이전 대문 구조에서는 Common.js가 본문 바깥에 #clbi-main-logo를 직접 삽입하고,&lt;br /&gt;
    본문 안의 #clbi-main-crt-hero를 #clbi-main-crt-hero-wrap으로 감싸서&lt;br /&gt;
    .liberty-content-main 위쪽으로 재배치했다.&lt;br /&gt;
&lt;br /&gt;
    새 대문은 본문 내부의 .main-portal이 로고, 알림, 카테고리 네비, 이미지 피드,&lt;br /&gt;
    방명록, 상태 패널을 모두 담당한다. 따라서 Common.js가 별도 로고나 CRT 래퍼를&lt;br /&gt;
    삽입하면 새 로고/콘텐츠와 중복된다.&lt;br /&gt;
&lt;br /&gt;
    여기서는 JS가 만들던 바깥 로고와 CRT 래퍼를 제거하고, 예전 대문 원본이나&lt;br /&gt;
    캐시된 렌더 결과에 남아 있을 수 있는 #clbi-main-crt-hero도 제거한다.&lt;br /&gt;
    */&lt;br /&gt;
    $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function setNativePageTitleHiddenHard(hidden) {&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    document.querySelectorAll(selectors.join(&#039;,&#039;)).forEach(function(node) {&lt;br /&gt;
        if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
        if (hidden) {&lt;br /&gt;
            node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;visibility&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;min-height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;margin&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;padding&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;overflow&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
        } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;min-height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;margin&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;padding&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyDefaultPageTitleVisibility() {&lt;br /&gt;
    var hideTitle = true;&lt;br /&gt;
    var isSystemAssetPage = false;&lt;br /&gt;
&lt;br /&gt;
    if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isSystemAssetPage === &#039;function&#039;) {&lt;br /&gt;
        isSystemAssetPage = window.CLBI_PAGE_SHELL.isSystemAssetPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage) {&lt;br /&gt;
        hideTitle = true;&lt;br /&gt;
    } else if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isBackendOrSystemPage === &#039;function&#039;) {&lt;br /&gt;
        hideTitle = !window.CLBI_PAGE_SHELL.isBackendOrSystemPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;)&lt;br /&gt;
        .toggleClass(&#039;page-title-hidden&#039;, hideTitle)&lt;br /&gt;
        .toggleClass(&#039;page-title-visible&#039;, !hideTitle)&lt;br /&gt;
        .toggleClass(&#039;clbi-system-doc-page&#039;, isSystemAssetPage);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.renderSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.renderSystemDocIndicator();&lt;br /&gt;
    } else if (!isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.removeSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.removeSystemDocIndicator();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (hideTitle) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(false);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyMainPageStyle() {&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    if (specialPage === &#039;Preferences&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var namespaceNumber = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var isMainPage = (pageName === &#039;대문&#039;);&lt;br /&gt;
    var isUserProfilePage = (namespaceNumber === 2);&lt;br /&gt;
    var isScreenDoc = ($(&#039;.screen-header&#039;).length &amp;gt; 0);&lt;br /&gt;
    var hideTools = (isMainPage || isUserProfilePage || !canShowContentTools());&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, isUserProfilePage);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;clbi-main-page&#039;, isMainPage);&lt;br /&gt;
&lt;br /&gt;
    // 모든 문서에서 분류 바를 본문 컨테이너 아래로 이동&lt;br /&gt;
    moveCatlinksToBottom();&lt;br /&gt;
&lt;br /&gt;
    if (isMainPage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 새 대문은 .main-portal 본문 구조가 로고/히어로를 담당한다.&lt;br /&gt;
        // Common.js의 구식 바깥 로고/CRT 재배치 루틴은 사용하지 않는다.&lt;br /&gt;
        removeLegacyMainPageHero();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    } else if (isUserProfilePage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else if (isScreenDoc) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        if ($(&#039;#clbi-tools-box&#039;).length === 0 &amp;amp;&amp;amp; canShowContentTools()) {&lt;br /&gt;
            var $toolsBox = $(&#039;&amp;lt;div id=&amp;quot;clbi-tools-box&amp;quot; class=&amp;quot;clbi-left-box&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsTitle = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;관리&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsContent = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-content&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
            $toolsContent.append($(&#039;.content-tools .btn-group&#039;).clone(true));&lt;br /&gt;
            $toolsBox.append($toolsTitle).append($toolsContent);&lt;br /&gt;
            $(&#039;#clbi-left-sidebar&#039;).append($toolsBox);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!isUserProfilePage) {&lt;br /&gt;
        $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
        $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    applyDefaultPageTitleVisibility();&lt;br /&gt;
    updateSidebar();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 본문 기본 목차 제거&lt;br /&gt;
function removeNativeTocFromContent() {&lt;br /&gt;
    $(&#039;.liberty-content-main #toc, .liberty-content-main .toc&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 ID 가져오기&lt;br /&gt;
function getHeadingId(heading) {&lt;br /&gt;
    if (heading.id) {&lt;br /&gt;
        return heading.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline[id]&#039;);&lt;br /&gt;
    if (headline &amp;amp;&amp;amp; headline.id) {&lt;br /&gt;
        return headline.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 제목 텍스트 가져오기&lt;br /&gt;
function getHeadingText(heading) {&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline&#039;);&lt;br /&gt;
    var source = headline || heading;&lt;br /&gt;
    var clone = source.cloneNode(true);&lt;br /&gt;
&lt;br /&gt;
    $(clone).find(&#039;.mw-editsection, .mw-editsection-bracket, .mw-editsection-divider&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    return (clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: 긴 제목에 자동 스크롤 적용&lt;br /&gt;
function initTocTitleScroll(root) {&lt;br /&gt;
    var $items = root&lt;br /&gt;
        ? $(root).find(&#039;.toc-scroll-text&#039;)&lt;br /&gt;
        : $(&#039;#side-toc-box .toc-scroll-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    $items.each(function () {&lt;br /&gt;
        var $text = $(this);&lt;br /&gt;
        var $wrap = $text.closest(&#039;.toc-scroll-wrap&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$wrap.length) return;&lt;br /&gt;
&lt;br /&gt;
        var wrapW = Math.floor($wrap.width());&lt;br /&gt;
        var textW = Math.ceil(this.scrollWidth);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 레이아웃 계산이 끝나지 않았으면 이번 실행에서는 건드리지 않는다.&lt;br /&gt;
        if (!wrapW || !textW) return;&lt;br /&gt;
&lt;br /&gt;
        if (textW &amp;lt;= wrapW + 12) {&lt;br /&gt;
            // 왼쪽 목차: 칸을 넘지 않는 제목은 전체 텍스트를 그대로 보여준다.&lt;br /&gt;
            $wrap.removeClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
            if ($text.data(&#039;toc-scroll-enabled&#039;)) {&lt;br /&gt;
                $text.css({&lt;br /&gt;
                    animation: &#039;&#039;,&lt;br /&gt;
                    &#039;animation-delay&#039;: &#039;&#039;,&lt;br /&gt;
                    &#039;--scroll-dist&#039;: &#039;&#039;&lt;br /&gt;
                });&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-enabled&#039;);&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-key&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var scrollDist = &#039;-&#039; + (textW - wrapW + 10) + &#039;px&#039;;&lt;br /&gt;
        var duration = Math.max(7, textW / 38) * 1.25;&lt;br /&gt;
        var scrollKey = scrollDist + &#039;|&#039; + duration;&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목에는 오른쪽 페이드와 스크롤을 적용한다.&lt;br /&gt;
        $wrap.addClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 같은 값으로 이미 적용된 애니메이션은 다시 초기화하지 않는다.&lt;br /&gt;
        if ($text.data(&#039;toc-scroll-key&#039;) === scrollKey) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $text.data(&#039;toc-scroll-enabled&#039;, true);&lt;br /&gt;
        $text.data(&#039;toc-scroll-key&#039;, scrollKey);&lt;br /&gt;
&lt;br /&gt;
        $text.css({&lt;br /&gt;
            // 왼쪽 목차: 페이지 진입 직후에는 잠시 읽을 시간을 준 뒤 흐르게 한다.&lt;br /&gt;
            animation: &#039;toc-scroll-blink-reset &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
            &#039;animation-delay&#039;: &#039;1s&#039;,&lt;br /&gt;
            &#039;--scroll-dist&#039;: scrollDist&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 목차를 왼쪽 사이드바에 새로 생성&lt;br /&gt;
function moveTocToLeftSidebar() {&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: MediaWiki가 만든 원래 목차는 본문에서 제거한다.&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
&lt;br /&gt;
    var leftSidebar = document.getElementById(&#039;clbi-left-sidebar&#039;);&lt;br /&gt;
    if (!leftSidebar) return;&lt;br /&gt;
&lt;br /&gt;
    var content =&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;) ||&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!content) return;&lt;br /&gt;
&lt;br /&gt;
    var headings = Array.prototype.slice.call(&lt;br /&gt;
        content.querySelectorAll(&#039;h2, h3&#039;)&lt;br /&gt;
    ).filter(function (heading) {&lt;br /&gt;
        if (heading.closest(&#039;#toc, .toc, #side-toc-box&#039;)) return false;&lt;br /&gt;
&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
&lt;br /&gt;
        if (!id || !text) return false;&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var tocKey = headings.map(function (heading) {&lt;br /&gt;
        return getHeadingId(heading) + &#039;|&#039; + getHeadingText(heading);&lt;br /&gt;
    }).join(&#039;||&#039;);&lt;br /&gt;
&lt;br /&gt;
    var existingBox = document.getElementById(&#039;side-toc-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 같은 문서에서 같은 목차를 이미 만들었다면 다시 지우고 만들지 않는다.&lt;br /&gt;
    if (existingBox &amp;amp;&amp;amp; existingBox.getAttribute(&#039;data-toc-key&#039;) === tocKey) {&lt;br /&gt;
        initTocTitleScroll(existingBox);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (existingBox) {&lt;br /&gt;
        existingBox.remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!headings.length) return;&lt;br /&gt;
&lt;br /&gt;
    var tocBox = document.createElement(&#039;div&#039;);&lt;br /&gt;
    tocBox.className = &#039;clbi-left-box&#039;;&lt;br /&gt;
    tocBox.id = &#039;side-toc-box&#039;;&lt;br /&gt;
    tocBox.setAttribute(&#039;data-toc-key&#039;, tocKey);&lt;br /&gt;
&lt;br /&gt;
    var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
    title.className = &#039;clbi-left-title&#039;;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 박스 제목은 Lang.js의 현재 UI 언어를 따른다.&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
    var tocTitleText = (t &amp;amp;&amp;amp; t.toc) ? t.toc : &#039;목차&#039;;&lt;br /&gt;
&lt;br /&gt;
    title.textContent = tocTitleText;&lt;br /&gt;
&lt;br /&gt;
    var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
    body.className = &#039;clbi-left-content toc-sidebar-content&#039;;&lt;br /&gt;
&lt;br /&gt;
    var list = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    list.className = &#039;generated-toc&#039;;&lt;br /&gt;
&lt;br /&gt;
    headings.forEach(function (heading) {&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
        var level = heading.tagName.toLowerCase() === &#039;h3&#039; ? 3 : 2;&lt;br /&gt;
&lt;br /&gt;
        var item = document.createElement(&#039;li&#039;);&lt;br /&gt;
        item.className = &#039;toc-level-&#039; + level;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.setAttribute(&#039;href&#039;, &#039;#&#039; + id);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목 스크롤을 위해 텍스트를 별도 span으로 감싼다.&lt;br /&gt;
        var textWrap = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textWrap.className = &#039;toc-scroll-wrap&#039;;&lt;br /&gt;
&lt;br /&gt;
        var textSpan = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textSpan.className = &#039;toc-scroll-text&#039;;&lt;br /&gt;
        textSpan.textContent = text;&lt;br /&gt;
&lt;br /&gt;
        textWrap.appendChild(textSpan);&lt;br /&gt;
        link.appendChild(textWrap);&lt;br /&gt;
&lt;br /&gt;
        item.appendChild(link);&lt;br /&gt;
        list.appendChild(item);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    body.appendChild(list);&lt;br /&gt;
    tocBox.appendChild(title);&lt;br /&gt;
    tocBox.appendChild(body);&lt;br /&gt;
    leftSidebar.appendChild(tocBox);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: DOM 배치가 끝난 뒤 긴 제목 스크롤 여부를 계산한다.&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        initTocTitleScroll(tocBox);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            initTocTitleScroll(tocBox);&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 우측 광고판: 이미지/번역 캡션 목록&lt;br /&gt;
var RIGHT_BILLBOARD_ITEMS = [&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
        alt: &#039;PROOF TO THE WORLD / YOU ONCE PART OF IT&#039;,&lt;br /&gt;
        duration: 3000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;당신이 한때 이 세계의&#039;,&lt;br /&gt;
            &#039;&#039;,&lt;br /&gt;
            &#039;일부였다는 것을 증명하십시오&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-003.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    }&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItem(index) {&lt;br /&gt;
    var items = RIGHT_BILLBOARD_ITEMS;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        return {&lt;br /&gt;
            file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
            alt: &#039;&#039;,&lt;br /&gt;
            caption: []&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var normalized = index % items.length;&lt;br /&gt;
    if (normalized &amp;lt; 0) normalized += items.length;&lt;br /&gt;
&lt;br /&gt;
    return items[normalized];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardImageUrl(fileName) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/&#039; + encodeURIComponent(fileName || &#039;Side-visual-001.png&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardCaptionHtml(item) {&lt;br /&gt;
    var lines = item &amp;amp;&amp;amp; item.caption ? item.caption : [];&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    function escapeCaptionText(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/\&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    lines.forEach(function(line) {&lt;br /&gt;
        var text = String(line == null ? &#039;&#039; : line);&lt;br /&gt;
        var isGap = !text.trim();&lt;br /&gt;
        var className = &#039;right-billboard-caption-line&#039; + (isGap ? &#039; is-gap&#039; : &#039;&#039;);&lt;br /&gt;
        html += &#039;&amp;lt;span class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; + (isGap ? &#039;&amp;amp;nbsp;&#039; : escapeCaptionText(text)) + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setRightBillboardItem(index) {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    var item = getRightBillboardItem(index);&lt;br /&gt;
    var src = getRightBillboardImageUrl(item.file);&lt;br /&gt;
    var images = box.querySelectorAll(&#039;.right-billboard-image&#039;);&lt;br /&gt;
    var caption = box.querySelector(&#039;#right-billboard-caption&#039;);&lt;br /&gt;
    var emptySub = box.querySelector(&#039;.right-billboard-empty-sub&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, String(index));&lt;br /&gt;
    box.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(images, function(img) {&lt;br /&gt;
        img.style.display = &#039;&#039;;&lt;br /&gt;
        img.setAttribute(&#039;src&#039;, src);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, img.classList.contains(&#039;right-billboard-image-base&#039;) ? (item.alt || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (caption) {&lt;br /&gt;
        caption.innerHTML = getRightBillboardCaptionHtml(item);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (emptySub) {&lt;br /&gt;
        emptySub.textContent = item.file || &#039;Side-visual-001.png&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItemDuration(item) {&lt;br /&gt;
    var duration = item &amp;amp;&amp;amp; item.duration ? parseInt(item.duration, 10) : 3000;&lt;br /&gt;
&lt;br /&gt;
    if (Number.isNaN(duration) || duration &amp;lt; 500) {&lt;br /&gt;
        duration = 3000;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return duration;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initRightBillboardCarousel() {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-billboard-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    setRightBillboardItem(0);&lt;br /&gt;
&lt;br /&gt;
    if (!RIGHT_BILLBOARD_ITEMS || RIGHT_BILLBOARD_ITEMS.length &amp;lt;= 1) return;&lt;br /&gt;
&lt;br /&gt;
    function scheduleNext() {&lt;br /&gt;
        var current = parseInt(box.getAttribute(&#039;data-billboard-index&#039;) || &#039;0&#039;, 10);&lt;br /&gt;
        if (Number.isNaN(current)) current = 0;&lt;br /&gt;
&lt;br /&gt;
        var currentItem = getRightBillboardItem(current);&lt;br /&gt;
        var delay = getRightBillboardItemDuration(currentItem);&lt;br /&gt;
&lt;br /&gt;
        window.setTimeout(function() {&lt;br /&gt;
            if (!document.body.contains(box)) return;&lt;br /&gt;
&lt;br /&gt;
            if (!document.hidden) {&lt;br /&gt;
                setRightBillboardItem(current + 1);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            scheduleNext();&lt;br /&gt;
        }, delay);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scheduleNext();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function escapeRightBillboardAttr(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildRightBillboardBox() {&lt;br /&gt;
    var billboardInitial = getRightBillboardItem(0);&lt;br /&gt;
    var billboardSrc = getRightBillboardImageUrl(billboardInitial.file);&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box right-billboard-box left-billboard-box left-ad-box&amp;quot; data-billboard-index=&amp;quot;0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title right-billboard-title left-ad-title left-ad-title-iconless&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-ad&amp;quot; class=&amp;quot;left-ad-title-label&amp;quot;&amp;gt;Looking for a job?&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content left-ad-content-shell&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;right-billboard-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-recess&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;right-billboard-screen&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;right-billboard-image&amp;quot; class=&amp;quot;right-billboard-image right-billboard-image-base&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&#039; + escapeRightBillboardAttr(billboardInitial.alt || &#039;&#039;) + &#039;&amp;quot; onload=&amp;quot;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.remove(\&#039;is-empty\&#039;);}&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.add(\&#039;is-empty\&#039;);}&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-image-bloom&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-a&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-b&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-c&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-glitch&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-tear&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-empty&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-main&amp;quot;&amp;gt;SIGNAL EMPTY&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-sub&amp;quot;&amp;gt;&#039; + escapeRightBillboardAttr(billboardInitial.file || &#039;Side-visual-001.png&#039;) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;right-billboard-caption&amp;quot; class=&amp;quot;right-billboard-caption&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + getRightBillboardCaptionHtml(billboardInitial) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-bottom-finish left-ad-bottom-finish&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var GREAT_WALL_DATA_TITLE = &#039;프로젝트:The_Great_Wall/Data.json&#039;;&lt;br /&gt;
var GREAT_WALL_LIST_LIMIT = 0;&lt;br /&gt;
var greatWallState = {&lt;br /&gt;
    data: { entries: {} },&lt;br /&gt;
    loaded: false,&lt;br /&gt;
    loading: false,&lt;br /&gt;
    saving: false,&lt;br /&gt;
    selectedOwnEntry: false,&lt;br /&gt;
    statusText: &#039;&#039;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function normalizeGreatWallData(data) {&lt;br /&gt;
    var normalized = { entries: {} };&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    Object.keys(entries).forEach(function(key) {&lt;br /&gt;
        var item = entries[key];&lt;br /&gt;
        var user = item &amp;amp;&amp;amp; item.user ? String(item.user) : String(key || &#039;&#039;);&lt;br /&gt;
        var text = item &amp;amp;&amp;amp; item.text ? String(item.text) : &#039;&#039;;&lt;br /&gt;
        var timestamp = item &amp;amp;&amp;amp; item.timestamp ? String(item.timestamp) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!user || !text) return;&lt;br /&gt;
&lt;br /&gt;
        normalized.entries[user] = {&lt;br /&gt;
            user: user,&lt;br /&gt;
            text: text.slice(0, 140),&lt;br /&gt;
            timestamp: timestamp || new Date(0).toISOString()&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseGreatWallData(text) {&lt;br /&gt;
    var parsed;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;The Great Wall data parse failed:&#039;, err);&lt;br /&gt;
        parsed = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return normalizeGreatWallData(parsed);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function stringifyGreatWallData(data) {&lt;br /&gt;
    return JSON.stringify(normalizeGreatWallData(data), null, 2) + &#039;\n&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallRevisionText(page) {&lt;br /&gt;
    var rev;&lt;br /&gt;
    var slot;&lt;br /&gt;
&lt;br /&gt;
    if (!page || !page.revisions || !page.revisions.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    rev = page.revisions[0];&lt;br /&gt;
&lt;br /&gt;
    if (rev.slots &amp;amp;&amp;amp; rev.slots.main) {&lt;br /&gt;
        slot = rev.slots.main;&lt;br /&gt;
        return slot.content || slot[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return rev.content || rev[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchGreatWallData() {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
    return api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;revisions&#039;,&lt;br /&gt;
        titles: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
        rvprop: &#039;content|timestamp&#039;,&lt;br /&gt;
        rvslots: &#039;main&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages[0] || null;&lt;br /&gt;
&lt;br /&gt;
        if (!page || page.missing) {&lt;br /&gt;
            return { entries: {} };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parseGreatWallData(getGreatWallRevisionText(page));&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall load failed:&#039;, err);&lt;br /&gt;
        return { entries: {} };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallEntries(data) {&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    return Object.keys(entries).map(function(key) {&lt;br /&gt;
        return entries[key];&lt;br /&gt;
    }).filter(function(item) {&lt;br /&gt;
        return item &amp;amp;&amp;amp; item.user &amp;amp;&amp;amp; item.text;&lt;br /&gt;
    }).sort(function(a, b) {&lt;br /&gt;
        return String(a.timestamp || &#039;&#039;).localeCompare(String(b.timestamp || &#039;&#039;));&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallMessageTime(timestamp) {&lt;br /&gt;
    var date = timestamp ? new Date(timestamp) : null;&lt;br /&gt;
    var month;&lt;br /&gt;
    var day;&lt;br /&gt;
    var hour;&lt;br /&gt;
    var minute;&lt;br /&gt;
&lt;br /&gt;
    if (!date || isNaN(date.getTime())) return &#039;—&#039;;&lt;br /&gt;
&lt;br /&gt;
    month = String(date.getMonth() + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
    day = String(date.getDate()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    hour = String(date.getHours()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    minute = String(date.getMinutes()).padStart(2, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    return month + &#039;.&#039; + day + &#039; &#039; + hour + &#039;:&#039; + minute;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallAvatarSrc(user) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(String(user || &#039;&#039;)) + &#039;.png&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallEntryHtml(item, currentUser, selectedOwnEntry) {&lt;br /&gt;
    var isOwn = currentUser &amp;amp;&amp;amp; item.user === currentUser;&lt;br /&gt;
    var tag = isOwn ? &#039;button&#039; : &#039;div&#039;;&lt;br /&gt;
    var attrs = isOwn ? &#039; type=&amp;quot;button&amp;quot; data-great-wall-own-entry=&amp;quot;1&amp;quot; aria-label=&amp;quot;Edit your wall message&amp;quot;&#039; : &#039;&#039;;&lt;br /&gt;
    var className = &#039;great-wall-entry&#039; + (isOwn ? &#039; is-own&#039; : &#039;&#039;) + (isOwn &amp;amp;&amp;amp; selectedOwnEntry ? &#039; is-selected&#039; : &#039;&#039;);&lt;br /&gt;
    var avatarSrc = getGreatWallAvatarSrc(item.user);&lt;br /&gt;
    var messageTime = getGreatWallMessageTime(item.timestamp);&lt;br /&gt;
    var isoTime = item.timestamp || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;&#039; + tag + attrs + &#039; class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;img class=&amp;quot;great-wall-avatar&amp;quot; src=&amp;quot;&#039; + escapeClbiHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=&amp;amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;quot;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-bubble&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-entry-head&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-user&amp;quot;&amp;gt;@&#039; + escapeClbiHtml(item.user) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-time&amp;quot; title=&amp;quot;&#039; + escapeClbiHtml(isoTime) + &#039;&amp;quot;&amp;gt;&#039; + escapeClbiHtml(messageTime) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-text&amp;quot;&amp;gt;&#039; + escapeClbiHtml(item.text) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/&#039; + tag + &#039;&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var list = document.getElementById(&#039;great-wall-list&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
    var status = document.getElementById(&#039;great-wall-status&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var entries = getGreatWallEntries(greatWallState.data);&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var visibleEntries = [];&lt;br /&gt;
    var seen = {};&lt;br /&gt;
&lt;br /&gt;
    if (!box || !list) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.toggle(&#039;is-guest&#039;, !currentUser);&lt;br /&gt;
    box.classList.toggle(&#039;is-loading&#039;, !!greatWallState.loading);&lt;br /&gt;
    box.classList.toggle(&#039;is-saving&#039;, !!greatWallState.saving);&lt;br /&gt;
    box.classList.toggle(&#039;has-own-entry&#039;, !!ownEntry);&lt;br /&gt;
    box.classList.toggle(&#039;is-own-selected&#039;, !!greatWallState.selectedOwnEntry);&lt;br /&gt;
&lt;br /&gt;
    entries.forEach(function(item) {&lt;br /&gt;
        if (seen[item.user]) return;&lt;br /&gt;
        visibleEntries.push(item);&lt;br /&gt;
        seen[item.user] = true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.loading &amp;amp;&amp;amp; !greatWallState.loaded) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else if (!visibleEntries.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;NO MARKS&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else {&lt;br /&gt;
        list.innerHTML = visibleEntries.map(function(item) {&lt;br /&gt;
            return buildGreatWallEntryHtml(item, currentUser, greatWallState.selectedOwnEntry);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list &amp;amp;&amp;amp; list.scrollHeight &amp;gt; list.clientHeight) {&lt;br /&gt;
        list.scrollTop = list.scrollHeight;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (status) {&lt;br /&gt;
        // Status text is intentionally suppressed in the composer strip; the area is spacing-only UI.&lt;br /&gt;
        status.textContent = &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!input || !submit) return;&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        submit.disabled = false;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = &#039;Login required&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.saving || greatWallState.loading) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = greatWallState.saving ? &#039;SAVE&#039; : &#039;SYNC&#039;;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = greatWallState.saving ? &#039;Saving&#039; : &#039;Syncing&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.disabled = !ownEntry;&lt;br /&gt;
        deleteButton.title = ownEntry ? &#039;Delete your mark&#039; : &#039;No mark to delete&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        input.disabled = false;&lt;br /&gt;
        input.readOnly = true;&lt;br /&gt;
        input.setAttribute(&#039;aria-readonly&#039;, &#039;true&#039;);&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = &#039;UPDATE&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
    input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; greatWallState.selectedOwnEntry &amp;amp;&amp;amp; !input.value) {&lt;br /&gt;
        input.value = ownEntry.text || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    submit.disabled = false;&lt;br /&gt;
    submit.textContent = ownEntry ? &#039;UPDATE&#039; : &#039;POST&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function saveGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var text = input ? String(input.value || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!text) {&lt;br /&gt;
        greatWallState.statusText = &#039;EMPTY MARK&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (text.length &amp;gt; 140) {&lt;br /&gt;
        text = text.slice(0, 140);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SAVING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        data.entries[currentUser] = {&lt;br /&gt;
            user: currentUser,&lt;br /&gt;
            text: text,&lt;br /&gt;
            timestamp: new Date().toISOString()&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Update The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK UPDATED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall save failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;SAVE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function deleteGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!ownEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;NO MARK TO DELETE&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;DELETING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        if (data.entries &amp;amp;&amp;amp; data.entries[currentUser]) {&lt;br /&gt;
            delete data.entries[currentUser];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Delete The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK DELETED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall delete failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;DELETE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBoxWhenReady() {&lt;br /&gt;
    if (!document.getElementById(&#039;great-wall-sidebar&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    if (mw.Api) {&lt;br /&gt;
        initGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.loader &amp;amp;&amp;amp; mw.loader.using) {&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
            initGreatWallBox();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-great-wall-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-great-wall-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var ownButton = e.target.closest ? e.target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;]&#039;) : null;&lt;br /&gt;
        var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
        var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
&lt;br /&gt;
        if (!ownButton || !ownEntry) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = true;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
        if (input) {&lt;br /&gt;
            input.focus();&lt;br /&gt;
            input.setSelectionRange(input.value.length, input.value.length);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var target = e.target;&lt;br /&gt;
        var keepSelection;&lt;br /&gt;
&lt;br /&gt;
        if (!greatWallState.selectedOwnEntry || !target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
        keepSelection = target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;], .great-wall-editor, .great-wall-compose-sector&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (keepSelection) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        if (input) input.value = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (submit) {&lt;br /&gt;
        submit.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
                window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            saveGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            deleteGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (input) {&lt;br /&gt;
        input.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
            if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
                e.preventDefault();&lt;br /&gt;
                saveGreatWallEntry();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&#039;input&#039;, function() {&lt;br /&gt;
            if (input.value.length &amp;gt; 140) {&lt;br /&gt;
                input.value = input.value.slice(0, 140);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.loading = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SYNCING WALL&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        greatWallState.data = normalizeGreatWallData(data);&lt;br /&gt;
        greatWallState.loaded = true;&lt;br /&gt;
        greatWallState.loading = false;&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;great-wall-sidebar&amp;quot; class=&amp;quot;clbi-right-box great-wall-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title great-wall-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-great-wall&amp;quot;&amp;gt;The Great Wall&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content great-wall-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-list&amp;quot; class=&amp;quot;great-wall-list&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-compose-sector&amp;quot; aria-label=&amp;quot;The Great Wall editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;input id=&amp;quot;great-wall-input&amp;quot; class=&amp;quot;great-wall-input&amp;quot; type=&amp;quot;text&amp;quot; maxlength=&amp;quot;140&amp;quot; autocomplete=&amp;quot;off&amp;quot; placeholder=&amp;quot;담벼락&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-submit&amp;quot; class=&amp;quot;great-wall-submit&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;POST&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-delete&amp;quot; class=&amp;quot;great-wall-delete&amp;quot; type=&amp;quot;button&amp;quot; disabled&amp;gt;DEL&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-status&amp;quot; class=&amp;quot;great-wall-status&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeMainPortalGuestbookPreview() {&lt;br /&gt;
    var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    var guestbook = portal ? portal.querySelector(&#039;.guestbook-device&#039;) : null;&lt;br /&gt;
    var sideScreen;&lt;br /&gt;
    var grid;&lt;br /&gt;
&lt;br /&gt;
    if (!portal || !guestbook) return;&lt;br /&gt;
&lt;br /&gt;
    sideScreen = guestbook.closest ? guestbook.closest(&#039;.side-screen&#039;) : null;&lt;br /&gt;
    grid = sideScreen &amp;amp;&amp;amp; sideScreen.closest ? sideScreen.closest(&#039;.console-grid&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (guestbook.parentNode) {&lt;br /&gt;
        guestbook.parentNode.removeChild(guestbook);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sideScreen &amp;amp;&amp;amp; !(sideScreen.textContent || &#039;&#039;).trim() &amp;amp;&amp;amp; !sideScreen.querySelector(&#039;img,svg,video,canvas,form,input,button,a&#039;)) {&lt;br /&gt;
        if (sideScreen.parentNode) {&lt;br /&gt;
            sideScreen.parentNode.removeChild(sideScreen);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (grid) {&lt;br /&gt;
            grid.style.gridTemplateColumns = &#039;minmax(0,1fr)&#039;;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    portal.classList.add(&#039;is-great-wall-relocated&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildSiteInformationBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-right-box site-info-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title site-info-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span&amp;gt;정보&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content site-info-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;policy-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/개인정보처리방침&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;개인정보처리방침&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/면책_조항&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;면책 조항&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/라이선스&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;라이선스&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/크레딧&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;크레딧&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;social-strip&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://discord.gg/ctaeJ9d3Q5&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;DC&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.youtube.com/@nxdsxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;YT&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://x.com/nxd_sxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;X&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;WIP:&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 초기화 함수&lt;br /&gt;
function initSidebars() {&lt;br /&gt;
    var header = $(&#039;.liberty-content-header&#039;);&lt;br /&gt;
    var content = $(&#039;.liberty-content&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (header.length &amp;amp;&amp;amp; content.length) {&lt;br /&gt;
        header.prependTo(content);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-right-sidebar&#039;).length === 0) {&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        var isLoggedIn = username !== null;&lt;br /&gt;
        var avatarSrc = isLoggedIn&lt;br /&gt;
            ? &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + username + &#039;.png&#039;&lt;br /&gt;
            : &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&#039;;&lt;br /&gt;
&lt;br /&gt;
        var userBox;&lt;br /&gt;
&lt;br /&gt;
        if (isLoggedIn) {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a href=&amp;quot;/index.php/사용자:&#039; + username + &#039;&amp;quot; id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;&#039; + username + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-quick-actions&amp;quot; aria-label=&amp;quot;프로필 빠른 메뉴&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-inventory&amp;quot; aria-label=&amp;quot;인벤토리&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_PACKAGE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;인벤토리&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-achievements&amp;quot; aria-label=&amp;quot;업적&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_TROPHY + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;업적&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-notifications&amp;quot; aria-label=&amp;quot;알림&amp;quot;&amp;gt;&amp;lt;span id=&amp;quot;profile-quick-notification-icon&amp;quot; class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_BELL + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;알림&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:기여/&#039; + username + &#039;&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-contribution&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_TEXT + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;기여&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:주시문서목록&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-watchlist&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_EYE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;주시문서 목록&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:설정&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-preferences&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SETTINGS + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;설정&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그아웃&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn clbi-user-btn-logout&amp;quot; id=&amp;quot;clbi-btn-logout&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그아웃&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row profile-name-row-guest&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;Guest&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그인&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-login&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그인&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        var greatWallBox = &#039;&#039;;&lt;br /&gt;
        var siteInformationBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            greatWallBox = buildGreatWallBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;The Great Wall build failed:&#039;, err);&lt;br /&gt;
            greatWallBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            siteInformationBox = buildSiteInformationBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Site information build failed:&#039;, err);&lt;br /&gt;
            siteInformationBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        var sidebar = userBox + greatWallBox + siteInformationBox;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).append(&#039;&amp;lt;div id=&amp;quot;clbi-right-sidebar&amp;quot;&amp;gt;&#039; + sidebar + &#039;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        removeMainPortalGuestbookPreview();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    initGreatWallBoxWhenReady();&lt;br /&gt;
    removeMainPortalGuestbookPreview();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-left-sidebar&#039;).length === 0) {&lt;br /&gt;
var leftBillboardBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            leftBillboardBox = buildRightBillboardBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Left billboard build failed:&#039;, err);&lt;br /&gt;
            leftBillboardBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
var leftSidebar =&lt;br /&gt;
    &#039;&amp;lt;div id=&amp;quot;clbi-left-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-language&amp;quot;&amp;gt;언어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content sidebar-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selector&amp;quot; class=&amp;quot;sidebar-lang-selector sidebar-lang-dial&amp;quot; tabindex=&amp;quot;0&amp;quot; role=&amp;quot;group&amp;quot; aria-label=&amp;quot;언어 선택&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-dial-stage&amp;quot; class=&amp;quot;sidebar-lang-dial-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-fan&amp;quot; class=&amp;quot;sidebar-lang-fan&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selected-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-left&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-selected-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;한국어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-availability-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-right is-current&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-availability-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;CURRENT&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-sidebar-lang-apply&amp;quot; class=&amp;quot;sidebar-lang-apply&amp;quot; aria-label=&amp;quot;언어 적용&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;sidebar-lang-apply-mark&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;✓&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-news&amp;quot;&amp;gt;뉴스&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content clbi-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-title&amp;quot;&amp;gt;CHANGELOG&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-changelog-feed&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;a href=&amp;quot;/index.php/체인지로그&amp;quot; class=&amp;quot;news-post-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-post-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-post-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-main&amp;quot;&amp;gt;체인지로그&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-post-jump&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-divider&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-recent-title&amp;quot;&amp;gt;RECENT CHANGES&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-recent-feed&amp;quot; id=&amp;quot;clbi-left-recent-list&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;a class=&amp;quot;news-fill-image-slot&amp;quot; id=&amp;quot;clbi-left-news-fill-image&amp;quot; href=&amp;quot;/index.php/특수:최근바뀜&amp;quot; aria-label=&amp;quot;최근 바뀜으로 이동&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-fill-image-frame&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-fill-image&amp;quot; style=&amp;quot;--news-fill-image-url:url(\&#039;/index.php?title=특수:Redirect/file/Side-news-fill-001.png\&#039;);&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;img class=&amp;quot;news-fill-image-probe&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Side-news-fill-001.png&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.closest(\&#039;.news-fill-image-slot\&#039;).classList.add(\&#039;is-empty\&#039;);this.remove();&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        leftBillboardBox +&lt;br /&gt;
    &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).prepend(leftSidebar);&lt;br /&gt;
&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
        loadRecentChangesList(&#039;#clbi-left-recent-list&#039;, 10);&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
        updateLeftSidebarNationsImage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        initRightBillboardCarousel();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;Right billboard carousel failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
    applyMainPageStyle();&lt;br /&gt;
    initClbiCustomDocumentScrollbars();&lt;br /&gt;
    initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
        window.ProgressSystemWebUi.boot(&#039;initSidebars&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initNotifications();&lt;br /&gt;
            initProfile();&lt;br /&gt;
            moveTocToLeftSidebar();&lt;br /&gt;
        }, 300);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 800);&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 1500);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    loadLangScript(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initSidebars();&lt;br /&gt;
        }, 100);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click.profileQuickPlaceholder&#039;, &#039;#profile-quick-inventory, #profile-quick-achievements&#039;, function(e) {&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
function extractJsonArrayAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var depth = 0;&lt;br /&gt;
    var inString = false;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = String(text || &#039;&#039;).indexOf(&#039;[&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (inString) {&lt;br /&gt;
            if (escaped) {&lt;br /&gt;
                escaped = false;&lt;br /&gt;
            } else if (ch === &#039;\\&#039;) {&lt;br /&gt;
                escaped = true;&lt;br /&gt;
            } else if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
                inString = false;&lt;br /&gt;
            }&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            inString = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;[&#039;) depth += 1;&lt;br /&gt;
        if (ch === &#039;]&#039;) {&lt;br /&gt;
            depth -= 1;&lt;br /&gt;
            if (depth === 0) {&lt;br /&gt;
                try {&lt;br /&gt;
                    return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    return null;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function extractJsonStringAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var colon;&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    colon = text.indexOf(&#039;:&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (colon === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = text.indexOf(&#039;&amp;quot;&#039;, colon + 1);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start + 1; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (escaped) {&lt;br /&gt;
            escaped = false;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;\\&#039;) {&lt;br /&gt;
            escaped = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                return text.slice(start + 1, i);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function syncCatlinksConfigFromSpaDocument(doc) {&lt;br /&gt;
    var scripts = doc ? doc.querySelectorAll(&#039;script&#039;) : [];&lt;br /&gt;
    var categories = null;&lt;br /&gt;
    var hiddenCategories = null;&lt;br /&gt;
    var relevantPageName = null;&lt;br /&gt;
    var pageName = null;&lt;br /&gt;
    var i;&lt;br /&gt;
    var text;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; scripts.length; i += 1) {&lt;br /&gt;
        text = scripts[i].textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (categories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) categories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (hiddenCategories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgHiddenCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) hiddenCategories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (relevantPageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgRelevantPageName&#039;);&lt;br /&gt;
            if (value !== null) relevantPageName = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgPageName&#039;);&lt;br /&gt;
            if (value !== null) pageName = value;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.config.set(&#039;wgCategories&#039;, Array.isArray(categories) ? categories : []);&lt;br /&gt;
    mw.config.set(&#039;wgHiddenCategories&#039;, Array.isArray(hiddenCategories) ? hiddenCategories : []);&lt;br /&gt;
&lt;br /&gt;
    if (relevantPageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, relevantPageName);&lt;br /&gt;
    } else if (pageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    CLBI_CATLINKS_FETCH_TOKEN += 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// SPA 네비게이션&lt;br /&gt;
function shouldSkip(url) {&lt;br /&gt;
    return url.match(/action=edit|action=submit|action=history|action=delete|action=protect|action=purge|특수:로그인|특수:로그아웃|Special:UserLogin|Special:UserLogout|특수:사용자정보|특수:비밀번호바꾸기|uselang=/);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    if (window._spaInitialized) return;&lt;br /&gt;
    window._spaInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    function isInternal(url) {&lt;br /&gt;
        var a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = url;&lt;br /&gt;
        return a.hostname === window.location.hostname;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getCachedSpaPageHtml(url) {&lt;br /&gt;
        if (!window.EntryStore || typeof window.EntryStore.getTextSync !== &#039;function&#039;) return &#039;&#039;;&lt;br /&gt;
        return window.EntryStore.getTextSync(url) || window.EntryStore.getTextSync(String(url || &#039;&#039;).replace(/^https?:\/\/[^/]+/i, &#039;&#039;)) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchSpaPageHtml(url) {&lt;br /&gt;
        var cached = getCachedSpaPageHtml(url);&lt;br /&gt;
        if (cached) return Promise.resolve(cached);&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; }).then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDetachedEntryContent(newContent) {&lt;br /&gt;
        /*&lt;br /&gt;
        Initial boot prepares entry artifacts; SPA is only allowed to consume them.&lt;br /&gt;
        This hook runs while the fetched page is still detached, before the user sees it.&lt;br /&gt;
        It must stay synchronous or already-resolved: if a subsystem cannot prepare from&lt;br /&gt;
        EntryStore immediately, it should leave the old fallback path in place instead of&lt;br /&gt;
        opening BootGate during SPA.&lt;br /&gt;
        */&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.prepareContentForEntry === &#039;function&#039;) {&lt;br /&gt;
                window.NationsPanel.prepareContentForEntry(newContent);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;entry content preparation failed:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadPage(url) {&lt;br /&gt;
        invalidateProfileRender();&lt;br /&gt;
&lt;br /&gt;
        return fetchSpaPageHtml(url)&lt;br /&gt;
            .then(function(html) {&lt;br /&gt;
                var parser = new DOMParser();&lt;br /&gt;
                var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
                var scripts = doc.querySelectorAll(&#039;script&#039;);&lt;br /&gt;
                for (var i = 0; i &amp;lt; scripts.length; i++) {&lt;br /&gt;
                    var src = scripts[i].textContent;&lt;br /&gt;
&lt;br /&gt;
                    if (src.indexOf(&#039;wgNamespaceNumber&#039;) !== -1) {&lt;br /&gt;
                        var match = src.match(/&amp;quot;wgNamespaceNumber&amp;quot;:(-?\d+)/);&lt;br /&gt;
                        if (match) mw.config.set(&#039;wgNamespaceNumber&#039;, parseInt(match[1], 10));&lt;br /&gt;
&lt;br /&gt;
                        var matchTitle = src.match(/&amp;quot;wgTitle&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchTitle) mw.config.set(&#039;wgTitle&#039;, matchTitle[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchPage = src.match(/&amp;quot;wgPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchPage) mw.config.set(&#039;wgPageName&#039;, matchPage[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchArticle = src.match(/&amp;quot;wgArticleId&amp;quot;:(\d+)/);&lt;br /&gt;
                        if (matchArticle) {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, parseInt(matchArticle[1], 10));&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, 0);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchIsMainPage = src.match(/&amp;quot;wgIsMainPage&amp;quot;:(true|false)/);&lt;br /&gt;
                        if (matchIsMainPage) {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, matchIsMainPage[1] === &#039;true&#039;);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchSpecial = src.match(/&amp;quot;wgCanonicalSpecialPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchSpecial) {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, matchSpecial[1]);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                syncCatlinksConfigFromSpaDocument(doc);&lt;br /&gt;
&lt;br /&gt;
                var newContent = doc.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
                var newTitle = doc.querySelector(&#039;.mw-page-title-main&#039;);&lt;br /&gt;
                var newHead = doc.querySelector(&#039;title&#039;);&lt;br /&gt;
                var newHeader = doc.querySelector(&#039;.liberty-content-header&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (newContent) {&lt;br /&gt;
                    prepareDetachedEntryContent(newContent);&lt;br /&gt;
                    prepareSpaCatlinksBeforeInsert(newContent);&lt;br /&gt;
                    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
                    $(&#039;.liberty-content-main&#039;).html(newContent.innerHTML);&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    try {&lt;br /&gt;
                        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.renderPrepared === &#039;function&#039;) window.Decorations.renderPrepared();&lt;br /&gt;
                        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.renderPrepared === &#039;function&#039;) window.CLBI_DECORATIONS.renderPrepared();&lt;br /&gt;
                    } catch (err) {}&lt;br /&gt;
                    $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newTitle) {&lt;br /&gt;
                    $(&#039;.mw-page-title-main&#039;).html(newTitle.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHead) {&lt;br /&gt;
                    document.title = newHead.textContent;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHeader) {&lt;br /&gt;
                    $(&#039;.liberty-content-header&#039;).html(newHeader.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
                window.scrollTo(0, 0);&lt;br /&gt;
                mw.hook(&#039;wikipage.content&#039;).fire($(&#039;.liberty-content-main&#039;));&lt;br /&gt;
                applyMainPageStyle();&lt;br /&gt;
                initClbiCustomDocumentScrollbars();&lt;br /&gt;
                initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
                if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.handleSpaPageView === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.handleSpaPageView();&lt;br /&gt;
                } else if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.boot(&#039;spa&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 100);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 500);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 1200);&lt;br /&gt;
&lt;br /&gt;
                mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
                    initProfile();&lt;br /&gt;
                    moveTocToLeftSidebar();&lt;br /&gt;
                });&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function (err) {&lt;br /&gt;
                console.error(&#039;SPA page load failed:&#039;, err);&lt;br /&gt;
                $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
// 목차 링크는 전용 처리&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;#side-toc-box a, #toc a, .toc a&#039;, function(e) {&lt;br /&gt;
    var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
    if (!href || href.charAt(0) !== &#039;#&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var rawId = href.slice(1);&lt;br /&gt;
    if (!rawId) return;&lt;br /&gt;
&lt;br /&gt;
    var decodedId = rawId;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        decodedId = decodeURIComponent(rawId);&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        decodedId = rawId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var target = document.getElementById(decodedId);&lt;br /&gt;
&lt;br /&gt;
    if (!target &amp;amp;&amp;amp; window.CSS &amp;amp;&amp;amp; CSS.escape) {&lt;br /&gt;
        target = document.querySelector(&#039;#&#039; + CSS.escape(decodedId));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!target) return;&lt;br /&gt;
&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
    var scrollTarget = target.closest(&#039;h2, h3&#039;) || target;&lt;br /&gt;
&lt;br /&gt;
    scrollTarget.scrollIntoView({&lt;br /&gt;
        behavior: &#039;auto&#039;,&lt;br /&gt;
        block: &#039;start&#039;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    history.replaceState(null, &#039;&#039;, &#039;#&#039; + rawId);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
    /* 길라잡이는 문서 링크가 아니라 대문 본문 화면 탭이다. */&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;, function(e) {&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.toggle === &#039;function&#039;) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopImmediatePropagation();&lt;br /&gt;
            window.BottomGuideNav.toggle();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;a&#039;, function(e) {&lt;br /&gt;
        // 휠 클릭, 새 탭 열기, 보조키 이동은 브라우저 기본 동작을 유지한다.&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        /* 길라잡이 화면 탭은 위 전용 처리 외에는 SPA 문서 이동 대상으로 삼지 않는다. */&lt;br /&gt;
        if ($(this).is(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
        if (!href) return;&lt;br /&gt;
&lt;br /&gt;
        // 목차 링크는 별도 핸들러에서 처리&lt;br /&gt;
        if ($(this).closest(&#039;#side-toc-box, #toc, .toc&#039;).length) return;&lt;br /&gt;
&lt;br /&gt;
        // 단순 해시 링크는 SPA 가로채기 제외&lt;br /&gt;
        if (href.startsWith(&#039;#&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.href = href;&lt;br /&gt;
&lt;br /&gt;
        var samePath = decodeURIComponent(link.pathname) === decodeURIComponent(window.location.pathname);&lt;br /&gt;
        var sameSearch = (link.search || &#039;&#039;) === (window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; samePath &amp;amp;&amp;amp; sameSearch) return;&lt;br /&gt;
&lt;br /&gt;
        var currentBase = window.location.href.split(&#039;#&#039;)[0];&lt;br /&gt;
        var targetBase = link.href.split(&#039;#&#039;)[0];&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; currentBase === targetBase) return;&lt;br /&gt;
&lt;br /&gt;
        if (!isInternal(href)) return;&lt;br /&gt;
        if (shouldSkip(href)) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        playStaticSound();&lt;br /&gt;
        /*&lt;br /&gt;
        SPA must remain a consumer phase.  If the target page HTML was prepared by&lt;br /&gt;
        the first-load boot pack, do not show the legacy page-loading veil.  The&lt;br /&gt;
        route will consume cached HTML and detached entry artifacts before insertion.&lt;br /&gt;
        */&lt;br /&gt;
        if (getCachedSpaPageHtml(href)) {&lt;br /&gt;
            $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;body&#039;).addClass(&#039;page-loading&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        history.pushState(null, &#039;&#039;, href);&lt;br /&gt;
        loadPage(href);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;popstate&#039;, function() {&lt;br /&gt;
        loadPage(window.location.href);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ========== CLBI Custom Document Scrollbar ========== */&lt;br /&gt;
function isGeneralDocumentView() {&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    if (!body) return false;&lt;br /&gt;
&lt;br /&gt;
    return body.classList.contains(&#039;action-view&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-main-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-system-doc-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;backend-system-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiDocumentScrollTargets() {&lt;br /&gt;
    if (!isGeneralDocumentView()) return [];&lt;br /&gt;
&lt;br /&gt;
    return Array.prototype.slice.call(document.querySelectorAll(&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; #mw-content-text .mw-parser-output, &#039; +&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; .mw-body-content .mw-parser-output&#039;&lt;br /&gt;
    )).filter(function (el, index, list) {&lt;br /&gt;
        return el &amp;amp;&amp;amp; list.indexOf(el) === index;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiOuterWellForScroll(scrollEl) {&lt;br /&gt;
    var main = scrollEl ? scrollEl.closest(&#039;.liberty-content-main&#039;) : null;&lt;br /&gt;
    var children;&lt;br /&gt;
    var i;&lt;br /&gt;
    var child;&lt;br /&gt;
&lt;br /&gt;
    if (!main) return null;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(main.children || []);&lt;br /&gt;
    for (i = 0; i &amp;lt; children.length; i += 1) {&lt;br /&gt;
        child = children[i];&lt;br /&gt;
        if (&lt;br /&gt;
            child &amp;amp;&amp;amp;&lt;br /&gt;
            (child.id === &#039;mw-content-text&#039; || child.classList.contains(&#039;mw-body-content&#039;)) &amp;amp;&amp;amp;&lt;br /&gt;
            child.contains(scrollEl)&lt;br /&gt;
        ) {&lt;br /&gt;
            return child;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return scrollEl.parentElement || null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiCustomScrollbar(well, scrollEl) {&lt;br /&gt;
    var bar = well.querySelector(&#039;:scope &amp;gt; .clbi-custom-scrollbar&#039;);&lt;br /&gt;
    var up;&lt;br /&gt;
    var track;&lt;br /&gt;
    var thumb;&lt;br /&gt;
    var down;&lt;br /&gt;
&lt;br /&gt;
    if (!bar) {&lt;br /&gt;
        bar = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bar.className = &#039;clbi-custom-scrollbar&#039;;&lt;br /&gt;
        bar.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        bar.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-up&amp;quot; data-scroll-arrow=&amp;quot;up&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-track&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;clbi-custom-scroll-thumb&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-down&amp;quot; data-scroll-arrow=&amp;quot;down&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        well.appendChild(bar);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.__clbiScrollTarget = scrollEl;&lt;br /&gt;
    up = bar.querySelector(&#039;.clbi-custom-scroll-arrow-up&#039;);&lt;br /&gt;
    track = bar.querySelector(&#039;.clbi-custom-scroll-track&#039;);&lt;br /&gt;
    thumb = bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;);&lt;br /&gt;
    down = bar.querySelector(&#039;.clbi-custom-scroll-arrow-down&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (up &amp;amp;&amp;amp; !up.__clbiBound) {&lt;br /&gt;
        up.__clbiBound = true;&lt;br /&gt;
        up.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop -= 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (down &amp;amp;&amp;amp; !down.__clbiBound) {&lt;br /&gt;
        down.__clbiBound = true;&lt;br /&gt;
        down.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop += 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (track &amp;amp;&amp;amp; !track.__clbiBound) {&lt;br /&gt;
        track.__clbiBound = true;&lt;br /&gt;
        track.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var rect;&lt;br /&gt;
            var thumbRect;&lt;br /&gt;
            var target;&lt;br /&gt;
            var direction;&lt;br /&gt;
&lt;br /&gt;
            if (e.target === thumb) return;&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            target = bar.__clbiScrollTarget;&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            rect = track.getBoundingClientRect();&lt;br /&gt;
            thumbRect = thumb.getBoundingClientRect();&lt;br /&gt;
            direction = e.clientY &amp;lt; thumbRect.top ? -1 : 1;&lt;br /&gt;
            target.scrollTop += direction * Math.max(60, Math.floor(target.clientHeight * 0.82));&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (thumb &amp;amp;&amp;amp; !thumb.__clbiBound) {&lt;br /&gt;
        thumb.__clbiBound = true;&lt;br /&gt;
        thumb.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var target = bar.__clbiScrollTarget;&lt;br /&gt;
            var startY;&lt;br /&gt;
            var startScroll;&lt;br /&gt;
            var maxScroll;&lt;br /&gt;
            var maxThumbTop;&lt;br /&gt;
            var trackHeight;&lt;br /&gt;
            var thumbHeight;&lt;br /&gt;
&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            startY = e.clientY;&lt;br /&gt;
            startScroll = target.scrollTop;&lt;br /&gt;
            maxScroll = Math.max(1, target.scrollHeight - target.clientHeight);&lt;br /&gt;
            trackHeight = track ? track.clientHeight : 0;&lt;br /&gt;
            thumbHeight = thumb.offsetHeight || 0;&lt;br /&gt;
            maxThumbTop = Math.max(1, trackHeight - thumbHeight);&lt;br /&gt;
&lt;br /&gt;
            bar.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
            function onMove(moveEvent) {&lt;br /&gt;
                var dy = moveEvent.clientY - startY;&lt;br /&gt;
                target.scrollTop = startScroll + (dy / maxThumbTop) * maxScroll;&lt;br /&gt;
                updateClbiCustomScrollbar(bar);&lt;br /&gt;
                moveEvent.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function onUp() {&lt;br /&gt;
                bar.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                document.removeEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
                document.removeEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            document.addEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
            document.addEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!scrollEl.__clbiCustomScrollbarBound) {&lt;br /&gt;
        scrollEl.__clbiCustomScrollbarBound = true;&lt;br /&gt;
        scrollEl.addEventListener(&#039;scroll&#039;, function () {&lt;br /&gt;
            if (scrollEl.__clbiCustomScrollbar) {&lt;br /&gt;
                updateClbiCustomScrollbar(scrollEl.__clbiCustomScrollbar);&lt;br /&gt;
            }&lt;br /&gt;
        }, { passive: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scrollEl.__clbiCustomScrollbar = bar;&lt;br /&gt;
    updateClbiCustomScrollbar(bar);&lt;br /&gt;
&lt;br /&gt;
    return bar;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiCustomScrollbar(bar) {&lt;br /&gt;
    var scrollEl = bar &amp;amp;&amp;amp; bar.__clbiScrollTarget;&lt;br /&gt;
    var track = bar ? bar.querySelector(&#039;.clbi-custom-scroll-track&#039;) : null;&lt;br /&gt;
    var thumb = bar ? bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;) : null;&lt;br /&gt;
    var maxScroll;&lt;br /&gt;
    var trackHeight;&lt;br /&gt;
    var thumbHeight;&lt;br /&gt;
    var maxTop;&lt;br /&gt;
    var top;&lt;br /&gt;
&lt;br /&gt;
    if (!bar || !scrollEl || !track || !thumb) return;&lt;br /&gt;
&lt;br /&gt;
    maxScroll = scrollEl.scrollHeight - scrollEl.clientHeight;&lt;br /&gt;
    if (maxScroll &amp;lt;= 1) {&lt;br /&gt;
        bar.classList.add(&#039;is-hidden&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.classList.remove(&#039;is-hidden&#039;);&lt;br /&gt;
&lt;br /&gt;
    trackHeight = Math.max(1, track.clientHeight || 1);&lt;br /&gt;
    thumbHeight = Math.max(12, Math.floor((scrollEl.clientHeight / Math.max(scrollEl.scrollHeight, 1)) * trackHeight));&lt;br /&gt;
    thumbHeight = Math.min(trackHeight, thumbHeight);&lt;br /&gt;
    maxTop = Math.max(0, trackHeight - thumbHeight);&lt;br /&gt;
    top = maxScroll &amp;gt; 0 ? Math.round((scrollEl.scrollTop / maxScroll) * maxTop) : 0;&lt;br /&gt;
&lt;br /&gt;
    thumb.style.height = thumbHeight + &#039;px&#039;;&lt;br /&gt;
    thumb.style.transform = &#039;translateY(&#039; + top + &#039;px)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiCustomDocumentScrollbars() {&lt;br /&gt;
    var existing = Array.prototype.slice.call(document.querySelectorAll(&#039;.clbi-custom-scrollbar&#039;));&lt;br /&gt;
    var targets = getClbiDocumentScrollTargets();&lt;br /&gt;
    var liveBars = [];&lt;br /&gt;
&lt;br /&gt;
    if (!targets.length) {&lt;br /&gt;
        existing.forEach(function (bar) { bar.remove(); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    targets.forEach(function (scrollEl) {&lt;br /&gt;
        var well = getClbiOuterWellForScroll(scrollEl);&lt;br /&gt;
        var bar;&lt;br /&gt;
&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
        bar = buildClbiCustomScrollbar(well, scrollEl);&lt;br /&gt;
        liveBars.push(bar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    existing.forEach(function (bar) {&lt;br /&gt;
        if (liveBars.indexOf(bar) === -1) bar.remove();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (!window.__clbiCustomScrollbarResizeBound) {&lt;br /&gt;
    window.__clbiCustomScrollbarResizeBound = true;&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        setTimeout(initClbiCustomDocumentScrollbars, 60);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시간 계산 함수&lt;br /&gt;
function timeAgo(timestamp) {&lt;br /&gt;
    var now = new Date();&lt;br /&gt;
    var date = new Date(timestamp);&lt;br /&gt;
    var diff = Math.floor((now - date) / 1000);&lt;br /&gt;
&lt;br /&gt;
    if (diff &amp;lt; 60) return diff + &#039;초 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 3600) return Math.floor(diff / 60) + &#039;분 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 86400) return Math.floor(diff / 3600) + &#039;시간 전&#039;;&lt;br /&gt;
    return Math.floor(diff / 86400) + &#039;일 전&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
function getFoldTexts() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return (window.LANG &amp;amp;&amp;amp; window.LANG[lang])&lt;br /&gt;
        ? window.LANG[lang]&lt;br /&gt;
        : (window.LANG ? window.LANG.ko : { expand: &#039;펼치기&#039;, collapse: &#039;접기&#039; });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function refreshOpenAncestors($start) {&lt;br /&gt;
    $start.parents(&#039;[id^=&amp;quot;collapsible&amp;quot;]&#039;).each(function () {&lt;br /&gt;
        var $parent = $(this);&lt;br /&gt;
        if (!$parent.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        // 이미 fully open 상태면 굳이 다시 잠그지 않음&lt;br /&gt;
        if ($parent.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $parent.css(&#039;max-height&#039;, this.scrollHeight + &#039;px&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindInnerResizeUpdates($target) {&lt;br /&gt;
    // 이미지 늦게 로드될 때 높이 갱신&lt;br /&gt;
    $target.find(&#039;img&#039;).off(&#039;.foldimg&#039;).on(&#039;load.foldimg&#039;, function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            if ($target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
                $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function openFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;opening&#039;);&lt;br /&gt;
    $target.addClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 열린 뒤 자연 확장 가능하게 만들기 위해 먼저 px로 열기&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.collapse);&lt;br /&gt;
&lt;br /&gt;
    bindInnerResizeUpdates($target);&lt;br /&gt;
&lt;br /&gt;
    // 바깥 펼접 즉시 갱신&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    // 전환 끝나면 none으로 풀어서 중첩 펼접/동적 내용 증가를 자연스럽게 허용&lt;br /&gt;
    $target.off(&#039;transitionend.foldopen&#039;).on(&#039;transitionend.foldopen&#039;, function (e) {&lt;br /&gt;
        if (e.target !== this) return;&lt;br /&gt;
        if (!$target.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        $target.css(&#039;max-height&#039;, &#039;none&#039;);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;open&#039;);&lt;br /&gt;
&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // 늦게 렌더되는 콘텐츠 대응&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 80);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 220);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function closeFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    // none 상태에서 닫으면 transition이 안 되므로 실제 높이로 고정&lt;br /&gt;
    if ($target.css(&#039;max-height&#039;) === &#039;none&#039; || $target.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;closing&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target.removeClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.expand);&lt;br /&gt;
&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;closed&#039;);&lt;br /&gt;
    }, 250);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function () {&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiToggle&#039;, &#039;.toggleBtn&#039;, function () {&lt;br /&gt;
            var $btn = $(this);&lt;br /&gt;
            var targetId = $btn.data(&#039;target&#039;);&lt;br /&gt;
            var $target = $(&#039;#&#039; + targetId);&lt;br /&gt;
            if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
            var scrollY = window.scrollY;&lt;br /&gt;
&lt;br /&gt;
            if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
                closeFold($target, $btn);&lt;br /&gt;
            } else {&lt;br /&gt;
                openFold($target, $btn);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            window.scrollTo(0, scrollY);&lt;br /&gt;
        });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// ========== 프로필 시스템 ==========&lt;br /&gt;
function initProfile() {&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var token = ++PROFILE_RENDER_TOKEN;&lt;br /&gt;
    var ns = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var title = mw.config.get(&#039;wgTitle&#039;);&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    var isProfileSettings = specialPage === &#039;사용자정보&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, ns === 2);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-settings-page&#039;, isProfileSettings);&lt;br /&gt;
&lt;br /&gt;
    if (ns === 2) {&lt;br /&gt;
        var profileUser = title.split(&#039;/&#039;)[0];&lt;br /&gt;
        renderProfile(profileUser, token);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isProfileSettings) {&lt;br /&gt;
        initUserProfilePage();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderProfile(username, token) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        list: &#039;users&#039;,&lt;br /&gt;
        ususers: username,&lt;br /&gt;
        usprop: &#039;editcount&#039;&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        if (token !== PROFILE_RENDER_TOKEN) return;&lt;br /&gt;
        if (mw.config.get(&#039;wgNamespaceNumber&#039;) !== 2) return;&lt;br /&gt;
&lt;br /&gt;
        var currentTitle = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).split(&#039;/&#039;)[0];&lt;br /&gt;
        if (currentTitle !== username) return;&lt;br /&gt;
&lt;br /&gt;
        var user = data.query.users[0];&lt;br /&gt;
        var contentEl = document.getElementById(&#039;mw-content-text&#039;);&lt;br /&gt;
        if (!contentEl) return;&lt;br /&gt;
&lt;br /&gt;
        var pageContent = contentEl.querySelector(&#039;.mw-parser-output&#039;) || contentEl;&lt;br /&gt;
        injectProfileCard(username, user, pageContent);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function injectProfileCard(username, userData, container) {&lt;br /&gt;
    var isOwnPage = mw.config.get(&#039;wgUserName&#039;) === username;&lt;br /&gt;
    var editCount = (userData &amp;amp;&amp;amp; userData.editcount) ? userData.editcount : 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.classList.add(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var safeUsername = escapeHtml(username);&lt;br /&gt;
    var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(username) + &#039;.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var fallbackSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var editBtn = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;a href=&amp;quot;/index.php/특수:사용자정보&amp;quot; class=&amp;quot;profile-edit-btn&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-label&amp;quot;&amp;gt;프로필 수정&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-arrow&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var progressHtml = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;div class=&amp;quot;profile-page-progress is-syncing&amp;quot; data-profile-progress&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;LEVEL RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-page-progress-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-level&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;profile-page-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-sub&amp;quot;&amp;gt;SYNCING&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-meta&amp;quot;&amp;gt;TODAY — · DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var card = document.createElement(&#039;div&#039;);&lt;br /&gt;
    card.className = &#039;profile-card profile-page-console&#039;;&lt;br /&gt;
    card.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-titlebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;USER PROFILE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;OFFICIAL ARCHIVE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-identity-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-avatar-bay&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;&#039; + fallbackSrc + &#039;\&#039;;&amp;quot; alt=&amp;quot;&#039; + safeUsername + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-info-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-nameplate&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;h2 class=&amp;quot;profile-username&amp;quot;&amp;gt;&#039; + safeUsername + &#039;&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
                        editBtn +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-name&amp;quot; data-field=&amp;quot;name&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-role&amp;quot; data-field=&amp;quot;role&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-discord&amp;quot; data-field=&amp;quot;discord&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-lower-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-bio-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;BIOGRAPHY&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-bio&amp;quot; data-field=&amp;quot;bio&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-stats-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-stats&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        progressHtml +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-stat-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot;&amp;gt;&#039; + editCount + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;수정 횟수&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot; data-contrib-pages-stat&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot; data-contrib-pages-value&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;기여 문서&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-info-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;TIME&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-time&amp;gt;UTC --:--&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;LANGUAGE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-language&amp;gt;—&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    container.insertBefore(card, container.firstChild);&lt;br /&gt;
    loadProfileFields(username, card);&lt;br /&gt;
    loadProfileContributionPages(username, card);&lt;br /&gt;
    updateProfilePageEnvironment(card, null);&lt;br /&gt;
&lt;br /&gt;
    if (isOwnPage) {&lt;br /&gt;
        loadProfileProgressForUserPage(card);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getProfileLanguageLabel() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || (lang ? lang.toUpperCase() : &#039;—&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfilePageEnvironment(card, summary) {&lt;br /&gt;
    if (!card) return;&lt;br /&gt;
&lt;br /&gt;
    var timezone = summary &amp;amp;&amp;amp; summary.timezone ? summary.timezone : &#039;UTC&#039;;&lt;br /&gt;
    var timeEl = card.querySelector(&#039;[data-profile-time]&#039;);&lt;br /&gt;
    var langEl = card.querySelector(&#039;[data-profile-language]&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (timeEl) {&lt;br /&gt;
        try {&lt;br /&gt;
            timeEl.textContent = timezone + &#039; &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: timezone&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            timeEl.textContent = &#039;UTC &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: &#039;UTC&#039;&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (langEl) {&lt;br /&gt;
        langEl.textContent = getProfileLanguageLabel();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileContributionPages(username, card) {&lt;br /&gt;
    if (!username || !card) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var valueEl = card.querySelector(&#039;[data-contrib-pages-value]&#039;);&lt;br /&gt;
    if (valueEl) valueEl.textContent = &#039;SYNC&#039;;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        var pages = Object.create(null);&lt;br /&gt;
        var cont = {};&lt;br /&gt;
        var guard = 0;&lt;br /&gt;
&lt;br /&gt;
        function requestNext() {&lt;br /&gt;
            guard++;&lt;br /&gt;
&lt;br /&gt;
            var params = Object.assign({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                list: &#039;usercontribs&#039;,&lt;br /&gt;
                ucuser: username,&lt;br /&gt;
                ucnamespace: 0,&lt;br /&gt;
                ucprop: &#039;title&#039;,&lt;br /&gt;
                uclimit: &#039;max&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }, cont);&lt;br /&gt;
&lt;br /&gt;
            return api.get(params).then(function (data) {&lt;br /&gt;
                var rows = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.usercontribs ? data.query.usercontribs : [];&lt;br /&gt;
&lt;br /&gt;
                rows.forEach(function (row) {&lt;br /&gt;
                    if (row &amp;amp;&amp;amp; row.title) pages[row.title] = true;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                if (data &amp;amp;&amp;amp; data.continue &amp;amp;&amp;amp; data.continue.uccontinue &amp;amp;&amp;amp; guard &amp;lt; 40) {&lt;br /&gt;
                    cont = data.continue;&lt;br /&gt;
                    return requestNext();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (valueEl) valueEl.textContent = Object.keys(pages).length;&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestNext().fail(function () {&lt;br /&gt;
            if (valueEl) valueEl.textContent = &#039;—&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileProgressForUserPage(card) {&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;progress_summary&#039;,&lt;br /&gt;
            format: &#039;json&#039;,&lt;br /&gt;
            formatversion: 2&lt;br /&gt;
        }).then(function (data) {&lt;br /&gt;
            var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
            if (!payload || !payload.available || !payload.summary) return;&lt;br /&gt;
            updateUserPageProgress(card, payload.summary);&lt;br /&gt;
            updateProfilePageEnvironment(card, payload.summary);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateUserPageProgress(card, summary) {&lt;br /&gt;
    var panel = card.querySelector(&#039;[data-profile-progress]&#039;);&lt;br /&gt;
    if (!panel || !summary) return;&lt;br /&gt;
&lt;br /&gt;
    var level = summary.level || 1;&lt;br /&gt;
    var totalXp = summary.totalXp || 0;&lt;br /&gt;
    var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
    var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
    var percent = Math.max(0, Math.min(100, summary.progressPercent || 0));&lt;br /&gt;
    var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
    var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
    var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
&lt;br /&gt;
    panel.classList.remove(&#039;is-syncing&#039;);&lt;br /&gt;
    panel.classList.toggle(&#039;is-max-level&#039;, isMaxLevel);&lt;br /&gt;
&lt;br /&gt;
    var levelEl = panel.querySelector(&#039;.profile-page-level&#039;);&lt;br /&gt;
    var totalEl = panel.querySelector(&#039;.profile-page-total-xp&#039;);&lt;br /&gt;
    var fillEl = panel.querySelector(&#039;.profile-page-xp-fill&#039;);&lt;br /&gt;
    var subEl = panel.querySelector(&#039;.profile-page-progress-sub&#039;);&lt;br /&gt;
    var metaEl = panel.querySelector(&#039;.profile-page-progress-meta&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (levelEl) levelEl.textContent = (isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level;&lt;br /&gt;
    if (totalEl) totalEl.textContent = totalXp + &#039; XP&#039;;&lt;br /&gt;
    if (fillEl) fillEl.style.width = percent + &#039;%&#039;;&lt;br /&gt;
    if (subEl) subEl.textContent = isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;);&lt;br /&gt;
    if (metaEl) metaEl.textContent = &#039;TODAY &#039; + dailyXp + &#039; XP · DISCOVERED &#039; + discoveries;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileFields(username, card) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;userprofile&#039;,&lt;br /&gt;
        user: username&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var profile = data.userprofile;&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: profile.name || &#039;&#039;,&lt;br /&gt;
            discord: profile.discord || &#039;&#039;,&lt;br /&gt;
            role: profile.role || &#039;&#039;,&lt;br /&gt;
            bio: profile.bio || &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    }).fail(function() {&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: &#039;&#039;,&lt;br /&gt;
            discord: &#039;&#039;,&lt;br /&gt;
            role: &#039;&#039;,&lt;br /&gt;
            bio: &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfileFields(card, data) {&lt;br /&gt;
    var nameEl = card.querySelector(&#039;[data-field=&amp;quot;name&amp;quot;]&#039;);&lt;br /&gt;
    var roleEl = card.querySelector(&#039;[data-field=&amp;quot;role&amp;quot;]&#039;);&lt;br /&gt;
    var discordEl = card.querySelector(&#039;[data-field=&amp;quot;discord&amp;quot;]&#039;);&lt;br /&gt;
    var bioEl = card.querySelector(&#039;[data-field=&amp;quot;bio&amp;quot;]&#039;);&lt;br /&gt;
    if (nameEl) nameEl.textContent = data.name || &#039;&#039;;&lt;br /&gt;
    if (roleEl) roleEl.textContent = data.role || &#039;&#039;;&lt;br /&gt;
    if (discordEl) discordEl.textContent = data.discord ? (&#039;디스코드: &#039; + data.discord) : &#039;&#039;;&lt;br /&gt;
    if (bioEl) bioEl.textContent = data.bio || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
// ========== 프로필 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
// ========== 알림 시스템 ==========&lt;br /&gt;
function ensureNotificationPopup() {&lt;br /&gt;
    if (document.getElementById(&#039;clbi-notification-popup&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    var popup = document.createElement(&#039;div&#039;);&lt;br /&gt;
    popup.id = &#039;clbi-notification-popup&#039;;&lt;br /&gt;
    popup.style.cssText =&lt;br /&gt;
        &#039;display:none;position:fixed;z-index:99999;width:320px;max-height:420px;&#039; +&lt;br /&gt;
        &#039;background:#0a0909;border:2px solid #854369;border-radius:5px;&#039; +&lt;br /&gt;
        &#039;box-shadow:0 0 0 1px #1a1a1a, 0 8px 24px rgba(0,0,0,0.55);overflow:hidden;&#039;;&lt;br /&gt;
&lt;br /&gt;
    popup.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:10px 12px;border-bottom:2px solid #854369;background:linear-gradient(to bottom, #171114 0%, #0a0909 100%);color:#E2E2E2;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:space-between;gap:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;알림&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-notification-readall&amp;quot; style=&amp;quot;background:#171717;border:1px solid #854369;border-radius:6px;color:#E2E2E2;font-size:11px;font-weight:700;padding:4px 8px;cursor:pointer;&amp;quot;&amp;gt;전체 읽음&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;clbi-notification-list&amp;quot; style=&amp;quot;max-height:320px;overflow-y:auto;padding:8px 0;color:#E2E2E2;font-size:12px;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:8px;border-top:1px solid #2a2a2a;background:#111;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;/index.php?title=Special:Notifications&amp;quot; id=&amp;quot;clbi-notification-more&amp;quot; style=&amp;quot;display:block;width:100%;text-align:center;padding:8px 10px;border-radius:6px;background:#171717;border:1px solid #854369;color:#E2E2E2 !important;text-decoration:none !important;font-size:12px;font-weight:700;&amp;quot;&amp;gt;더보기&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    document.body.appendChild(popup);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function positionNotificationPopup() {&lt;br /&gt;
    var btn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
    var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
    if (!btn || !popup) return;&lt;br /&gt;
&lt;br /&gt;
    var rect = btn.getBoundingClientRect();&lt;br /&gt;
    var top = rect.bottom + 6;&lt;br /&gt;
    var left = rect.left + (rect.width / 2) - (popup.offsetWidth / 2);&lt;br /&gt;
&lt;br /&gt;
    if (left &amp;lt; 8) left = 8;&lt;br /&gt;
    if (left + popup.offsetWidth &amp;gt; window.innerWidth - 8) {&lt;br /&gt;
        left = window.innerWidth - popup.offsetWidth - 8;&lt;br /&gt;
    }&lt;br /&gt;
    if (top + popup.offsetHeight &amp;gt; window.innerHeight - 8) {&lt;br /&gt;
        top = Math.max(8, rect.top - popup.offsetHeight - 6);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    popup.style.top = top + &#039;px&#039;;&lt;br /&gt;
    popup.style.left = left + &#039;px&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseNotificationItemsFromHtml(html) {&lt;br /&gt;
    var parser = new DOMParser();&lt;br /&gt;
    var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.mw-echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;.mw-echo-ui-notificationsInboxWidgetRow&#039;,&lt;br /&gt;
        &#039;.echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;li[data-notification-id]&#039;,&lt;br /&gt;
        &#039;.mw-echo-notifications-list li&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var items = [];&lt;br /&gt;
    for (var i = 0; i &amp;lt; selectors.length; i++) {&lt;br /&gt;
        items = Array.prototype.slice.call(doc.querySelectorAll(selectors[i]));&lt;br /&gt;
        if (items.length) break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return items.slice(0, 5).map(function(item) {&lt;br /&gt;
        var link = item.querySelector(&#039;a[href]&#039;);&lt;br /&gt;
        var href = link ? link.getAttribute(&#039;href&#039;) : &#039;/index.php?title=Special:Notifications&#039;;&lt;br /&gt;
        var text = (item.textContent || &#039;&#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        var notificationId =&lt;br /&gt;
            item.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
            &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!notificationId) {&lt;br /&gt;
            var anyWithId = item.querySelector(&#039;[data-notification-id], [data-id], [data-notification]&#039;);&lt;br /&gt;
            if (anyWithId) {&lt;br /&gt;
                notificationId =&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (href &amp;amp;&amp;amp; href.indexOf(&#039;http&#039;) !== 0) {&lt;br /&gt;
            href = href.charAt(0) === &#039;/&#039;&lt;br /&gt;
                ? href&lt;br /&gt;
                : &#039;/index.php&#039; + (href.charAt(0) === &#039;?&#039; ? href : &#039;/&#039; + href);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            id: notificationId,&lt;br /&gt;
            href: href,&lt;br /&gt;
            text: text || &#039;알림&#039;&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setNotificationIcon(hasItems) {&lt;br /&gt;
    var quickIcon = document.getElementById(&#039;profile-quick-notification-icon&#039;);&lt;br /&gt;
    var svg = hasItems ? CLBI_SVG_BELL_DOT : CLBI_SVG_BELL;&lt;br /&gt;
&lt;br /&gt;
    if (quickIcon) {&lt;br /&gt;
        quickIcon.innerHTML = svg;&lt;br /&gt;
        quickIcon.classList.toggle(&#039;has-notifications&#039;, !!hasItems);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderNotificationPopup(items) {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    var badge = document.getElementById(&#039;clbi-notification-badge&#039;);&lt;br /&gt;
    if (!list) return;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;표시할 알림이 없습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        if (badge) badge.style.display = &#039;none&#039;;&lt;br /&gt;
        setNotificationIcon(false);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
    for (var i = 0; i &amp;lt; items.length; i++) {&lt;br /&gt;
        html +=&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;&#039; + items[i].href + &#039;&amp;quot; class=&amp;quot;clbi-notification-item&amp;quot; data-notification-id=&amp;quot;&#039; + (items[i].id || &#039;&#039;) + &#039;&amp;quot; style=&amp;quot;display:block;padding:10px 12px;color:#E2E2E2 !important;text-decoration:none !important;border-bottom:1px solid #1f1f1f;line-height:1.5;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                items[i].text +&lt;br /&gt;
            &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    list.innerHTML = html;&lt;br /&gt;
&lt;br /&gt;
    if (badge) {&lt;br /&gt;
        badge.textContent = items.length;&lt;br /&gt;
        badge.style.display = &#039;block&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    setNotificationIcon(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadNotificationsIntoPopup() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetch(&#039;/index.php?title=Special:Notifications&#039;, { credentials: &#039;same-origin&#039; })&lt;br /&gt;
        .then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        })&lt;br /&gt;
        .then(function(html) {&lt;br /&gt;
            var items = parseNotificationItemsFromHtml(html);&lt;br /&gt;
            renderNotificationPopup(items);&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function(err) {&lt;br /&gt;
            console.error(err);&lt;br /&gt;
            if (list) {&lt;br /&gt;
                list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;알림을 불러오지 못했습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markAllNotificationsRead() {&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: &#039;all&#039;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markNotificationReadById(notificationId) {&lt;br /&gt;
    if (!notificationId) {&lt;br /&gt;
        return $.Deferred().resolve().promise();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: notificationId&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initNotifications() {&lt;br /&gt;
    var quickBtn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!quickBtn) return;&lt;br /&gt;
&lt;br /&gt;
    ensureNotificationPopup();&lt;br /&gt;
    loadNotificationsIntoPopup();&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationToggle&#039;, &#039;#profile-quick-notifications&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (popup.style.display === &#039;none&#039; || popup.style.display === &#039;&#039;) {&lt;br /&gt;
                popup.style.display = &#039;block&#039;;&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
            } else {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationOutside&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationOutside&#039;, function(e) {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            var quickToggle = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (!popup.contains(e.target) &amp;amp;&amp;amp; (!quickToggle || !quickToggle.contains(e.target))) {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationReadAll&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationReadAll&#039;, &#039;#clbi-notification-readall&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var button = this;&lt;br /&gt;
            button.disabled = true;&lt;br /&gt;
            button.textContent = &#039;처리 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            markAllNotificationsRead()&lt;br /&gt;
                .then(function() {&lt;br /&gt;
                    loadNotificationsIntoPopup();&lt;br /&gt;
                })&lt;br /&gt;
                .always(function() {&lt;br /&gt;
                    button.disabled = false;&lt;br /&gt;
                    button.textContent = &#039;전체 읽음&#039;;&lt;br /&gt;
                });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationItem&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationItem&#039;, &#039;.clbi-notification-item&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var href = this.getAttribute(&#039;href&#039;);&lt;br /&gt;
            var notificationId = this.getAttribute(&#039;data-notification-id&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            markNotificationReadById(notificationId).always(function() {&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
                if (href) {&lt;br /&gt;
                    window.location.href = href;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(window)&lt;br /&gt;
        .off(&#039;resize.clbiNotification&#039;)&lt;br /&gt;
        .on(&#039;resize.clbiNotification&#039;, function() {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (popup &amp;amp;&amp;amp; popup.style.display === &#039;block&#039;) {&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
// ========== 알림 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
function initUserProfilePage() {&lt;br /&gt;
    $(&#039;body&#039;).addClass(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
&lt;br /&gt;
    var saveBtn = document.getElementById(&#039;pref-save&#039;);&lt;br /&gt;
    if (!saveBtn) return;&lt;br /&gt;
&lt;br /&gt;
    function getPrefRow(id) {&lt;br /&gt;
        var el = document.getElementById(id);&lt;br /&gt;
        if (!el) return null;&lt;br /&gt;
        return el.closest(&#039;.clbi-pref-row&#039;) || el.parentNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removePrefRow(id) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (row &amp;amp;&amp;amp; row.parentNode) {&lt;br /&gt;
            row.parentNode.removeChild(row);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPrefSection(className, titleText) {&lt;br /&gt;
        var section = document.createElement(&#039;div&#039;);&lt;br /&gt;
        section.className = &#039;clbi-pref-section &#039; + className;&lt;br /&gt;
&lt;br /&gt;
        var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;clbi-pref-section-title&#039;;&lt;br /&gt;
        title.textContent = titleText;&lt;br /&gt;
&lt;br /&gt;
        var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
        body.className = &#039;clbi-pref-section-body&#039;;&lt;br /&gt;
&lt;br /&gt;
        section.appendChild(title);&lt;br /&gt;
        section.appendChild(body);&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            section: section,&lt;br /&gt;
            body: body&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveRowToSection(id, targetBody, className) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (!row || !targetBody) return false;&lt;br /&gt;
&lt;br /&gt;
        row.classList.add(&#039;clbi-pref-row-key-&#039; + className);&lt;br /&gt;
        targetBody.appendChild(row);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebuildProfileSettingsLayout() {&lt;br /&gt;
        var root = document.querySelector(&#039;.clbi-prefs-profile&#039;);&lt;br /&gt;
        if (!root || root.dataset.profileSettingsReworked === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        root.dataset.profileSettingsReworked = &#039;1&#039;;&lt;br /&gt;
        root.classList.add(&#039;profile-settings-console&#039;);&lt;br /&gt;
&lt;br /&gt;
        removePrefRow(&#039;pref-badges&#039;);&lt;br /&gt;
&lt;br /&gt;
        var originalRows = Array.prototype.slice.call(root.querySelectorAll(&#039;.clbi-pref-row&#039;));&lt;br /&gt;
        var actionNodes = [];&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn.parentNode === root || saveBtn.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(saveBtn);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var statusNode = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        if (statusNode &amp;amp;&amp;amp; statusNode.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(statusNode);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var main = document.createElement(&#039;div&#039;);&lt;br /&gt;
        main.className = &#039;clbi-pref-main-grid&#039;;&lt;br /&gt;
&lt;br /&gt;
        var media = createPrefSection(&#039;clbi-pref-section-media&#039;, &#039;PROFILE IMAGE&#039;);&lt;br /&gt;
        var identity = createPrefSection(&#039;clbi-pref-section-identity&#039;, &#039;IDENTITY RECORD&#039;);&lt;br /&gt;
        var bio = createPrefSection(&#039;clbi-pref-section-bio&#039;, &#039;BIOGRAPHY&#039;);&lt;br /&gt;
        var account = createPrefSection(&#039;clbi-pref-section-account&#039;, &#039;ACCOUNT CONTACT&#039;);&lt;br /&gt;
        var misc = createPrefSection(&#039;clbi-pref-section-misc&#039;, &#039;OTHER OPTIONS&#039;);&lt;br /&gt;
&lt;br /&gt;
        main.appendChild(media.section);&lt;br /&gt;
        main.appendChild(identity.section);&lt;br /&gt;
        main.appendChild(bio.section);&lt;br /&gt;
        main.appendChild(account.section);&lt;br /&gt;
        main.appendChild(misc.section);&lt;br /&gt;
&lt;br /&gt;
        root.innerHTML = &#039;&#039;;&lt;br /&gt;
        root.appendChild(main);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-preview&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-btn&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-input&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-name&#039;, identity.body, &#039;name&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-role&#039;, identity.body, &#039;role&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-discord&#039;, identity.body, &#039;discord&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-bio&#039;, bio.body, &#039;bio&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-new-email&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-password&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-save&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
&lt;br /&gt;
        originalRows.forEach(function (row) {&lt;br /&gt;
            if (!row.parentNode &amp;amp;&amp;amp; !row.className.match(/clbi-pref-row-key-/)) {&lt;br /&gt;
                misc.body.appendChild(row);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!misc.body.children.length) {&lt;br /&gt;
            misc.section.parentNode.removeChild(misc.section);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var actions = document.createElement(&#039;div&#039;);&lt;br /&gt;
        actions.className = &#039;clbi-pref-actions&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn) actions.appendChild(saveBtn);&lt;br /&gt;
        if (statusNode) actions.appendChild(statusNode);&lt;br /&gt;
&lt;br /&gt;
        root.appendChild(actions);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    rebuildProfileSettingsLayout();&lt;br /&gt;
&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    var selectedFile = null;&lt;br /&gt;
    var cropper = null;&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-gallery-modal&#039;)) {&lt;br /&gt;
        var gModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        gModal.id = &#039;clbi-gallery-modal&#039;;&lt;br /&gt;
        gModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        gModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:480px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;justify-content:space-between;align-items:center;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;프로필 사진 선택&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-close&amp;quot; style=&amp;quot;background:none;border:none;color:#aaa;font-size:18px;cursor:pointer;&amp;quot;&amp;gt;✕&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-upload-btn&amp;quot; style=&amp;quot;background:#2a2a2a;border:2px dashed #854369;border-radius:8px;padding:32px;color:#e2e2e2;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:8px;font-size:13px;width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:32px;&amp;quot;&amp;gt;🖼️&amp;lt;/span&amp;gt;새 사진 업로드&#039; +&lt;br /&gt;
                &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history-section&amp;quot; style=&amp;quot;display:none;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div style=&amp;quot;font-size:11px;color:#888;margin-bottom:8px;&amp;quot;&amp;gt;이전 사진 — 클릭하면 바로 적용&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history&amp;quot; style=&amp;quot;display:flex;gap:8px;flex-wrap:wrap;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(gModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-crop-modal&#039;)) {&lt;br /&gt;
        var cModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        cModal.id = &#039;clbi-crop-modal&#039;;&lt;br /&gt;
        cModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        cModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:500px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;사진 조정&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;width:100%;max-height:380px;overflow:hidden;border-radius:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img id=&amp;quot;clbi-crop-image&amp;quot; style=&amp;quot;max-width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;gap:8px;justify-content:flex-end;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-cancel&amp;quot; style=&amp;quot;background:#2a2a2a;color:#e2e2e2;border:1px solid #444;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;취소&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-confirm&amp;quot; style=&amp;quot;background:#854369;color:#fff;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;확정&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(cModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gModal = document.getElementById(&#039;clbi-gallery-modal&#039;);&lt;br /&gt;
    var cModal = document.getElementById(&#039;clbi-crop-modal&#039;);&lt;br /&gt;
    var cropImage = document.getElementById(&#039;clbi-crop-image&#039;);&lt;br /&gt;
    var pfpInput = document.getElementById(&#039;pref-pfp-input&#039;);&lt;br /&gt;
&lt;br /&gt;
    function openGallery() {&lt;br /&gt;
        gModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;query&#039;,&lt;br /&gt;
            titles: &#039;파일:Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
            prop: &#039;imageinfo&#039;,&lt;br /&gt;
            iiprop: &#039;url|timestamp&#039;,&lt;br /&gt;
            iilimit: 6&lt;br /&gt;
        }).then(function(data) {&lt;br /&gt;
            var pages = data.query.pages;&lt;br /&gt;
            var page = pages[Object.keys(pages)[0]];&lt;br /&gt;
            if (!page.imageinfo || page.imageinfo.length === 0) return;&lt;br /&gt;
&lt;br /&gt;
            var historyEl = document.getElementById(&#039;clbi-gallery-history&#039;);&lt;br /&gt;
            var sectionEl = document.getElementById(&#039;clbi-gallery-history-section&#039;);&lt;br /&gt;
            historyEl.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            page.imageinfo.forEach(function(info, idx) {&lt;br /&gt;
                var wrap = document.createElement(&#039;div&#039;);&lt;br /&gt;
                wrap.style.cssText = &#039;position:relative;cursor:pointer;&#039;;&lt;br /&gt;
&lt;br /&gt;
                var img = document.createElement(&#039;img&#039;);&lt;br /&gt;
                img.src = info.url;&lt;br /&gt;
                img.style.cssText =&lt;br /&gt;
                    &#039;width:72px;height:72px;object-fit:cover;border-radius:8px;border:2px solid #444;flex-shrink:0;&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (idx === 0) {&lt;br /&gt;
                    img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                    var badge = document.createElement(&#039;div&#039;);&lt;br /&gt;
                    badge.textContent = &#039;현재&#039;;&lt;br /&gt;
                    badge.style.cssText =&lt;br /&gt;
                        &#039;position:absolute;bottom:4px;left:50%;transform:translateX(-50%);background:#854369;color:#fff;font-size:9px;padding:1px 6px;border-radius:10px;&#039;;&lt;br /&gt;
                    wrap.appendChild(badge);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseenter&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseleave&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#444&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
                    fetch(info.url)&lt;br /&gt;
                        .then(function(r) {&lt;br /&gt;
                            return r.blob();&lt;br /&gt;
                        })&lt;br /&gt;
                        .then(function(blob) {&lt;br /&gt;
                            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
                            gModal.style.display = &#039;none&#039;;&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                wrap.appendChild(img);&lt;br /&gt;
                historyEl.appendChild(wrap);&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            sectionEl.style.display = &#039;block&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function openCrop(src) {&lt;br /&gt;
        cropImage.src = src;&lt;br /&gt;
        cModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            cropper = new Cropper(cropImage, {&lt;br /&gt;
                aspectRatio: 1,&lt;br /&gt;
                viewMode: 1,&lt;br /&gt;
                dragMode: &#039;move&#039;,&lt;br /&gt;
                autoCropArea: 0.8,&lt;br /&gt;
                cropBoxResizable: true,&lt;br /&gt;
                cropBoxMovable: true&lt;br /&gt;
            });&lt;br /&gt;
        }, 150);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;pref-pfp-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        openGallery();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-upload-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        pfpInput.click();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-close&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    pfpInput.addEventListener(&#039;change&#039;, function() {&lt;br /&gt;
        var file = this.files[0];&lt;br /&gt;
        if (!file) return;&lt;br /&gt;
&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
&lt;br /&gt;
        var reader = new FileReader();&lt;br /&gt;
        reader.onload = function(e) {&lt;br /&gt;
            openCrop(e.target.result);&lt;br /&gt;
        };&lt;br /&gt;
        reader.readAsDataURL(file);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-cancel&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        cModal.style.display = &#039;none&#039;;&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
        pfpInput.value = &#039;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-confirm&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        if (!cropper) return;&lt;br /&gt;
&lt;br /&gt;
        var canvas = cropper.getCroppedCanvas({ width: 256, height: 256 });&lt;br /&gt;
        if (!canvas) return;&lt;br /&gt;
&lt;br /&gt;
        canvas.toBlob(function(blob) {&lt;br /&gt;
            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
            cModal.style.display = &#039;none&#039;;&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
        }, &#039;image/png&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var emailSaveBtn = document.getElementById(&#039;pref-email-save&#039;);&lt;br /&gt;
    if (emailSaveBtn) {&lt;br /&gt;
        emailSaveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
            var statusEl = document.getElementById(&#039;pref-email-status&#039;);&lt;br /&gt;
            var newEmail = document.getElementById(&#039;pref-new-email&#039;).value;&lt;br /&gt;
            var password = document.getElementById(&#039;pref-email-password&#039;).value;&lt;br /&gt;
&lt;br /&gt;
            if (!newEmail || !password) {&lt;br /&gt;
                statusEl.textContent = &#039;이메일과 비밀번호를 입력해주세요.&#039;;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            statusEl.textContent = &#039;변경 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;changeemail&#039;,&lt;br /&gt;
                email: newEmail,&lt;br /&gt;
                password: password&lt;br /&gt;
            }).then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 이메일 변경됨&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-new-email&#039;).value = &#039;&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-email-password&#039;).value = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 3000);&lt;br /&gt;
            }).fail(function(code, data) {&lt;br /&gt;
                var msg = data &amp;amp;&amp;amp; data.error &amp;amp;&amp;amp; data.error.info ? data.error.info : &#039;변경 실패&#039;;&lt;br /&gt;
                statusEl.textContent = msg;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    saveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        var statusEl = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        statusEl.textContent = &#039;저장 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
        var promises = [];&lt;br /&gt;
&lt;br /&gt;
        if (selectedFile) {&lt;br /&gt;
            var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;upload&#039;,&lt;br /&gt;
                    filename: &#039;Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
                    ignorewarnings: true,&lt;br /&gt;
                    file: selectedFile,&lt;br /&gt;
                    format: &#039;json&#039;&lt;br /&gt;
                }, {&lt;br /&gt;
                    contentType: &#039;multipart/form-data&#039;&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var fields = [&#039;name&#039;, &#039;discord&#039;, &#039;role&#039;, &#039;bio&#039;];&lt;br /&gt;
&lt;br /&gt;
        for (var i = 0; i &amp;lt; fields.length; i++) {&lt;br /&gt;
            var el = document.getElementById(&#039;pref-&#039; + fields[i]);&lt;br /&gt;
            if (!el) continue;&lt;br /&gt;
&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;options&#039;,&lt;br /&gt;
                    optionname: &#039;profile-&#039; + fields[i],&lt;br /&gt;
                    optionvalue: el.value&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $.when.apply($, promises)&lt;br /&gt;
            .then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 저장됨&#039;;&lt;br /&gt;
                selectedFile = null;&lt;br /&gt;
                document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;사진 선택&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 2000);&lt;br /&gt;
            })&lt;br /&gt;
            .fail(function() {&lt;br /&gt;
                statusEl.textContent = &#039;저장 실패&#039;;&lt;br /&gt;
            });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Banner / CRT Page Monitor thumbnail slices&lt;br /&gt;
   - base 이미지는 틀에 들어간 파일 문법 그대로 사용&lt;br /&gt;
   - slice 레이어에는 300px MediaWiki 썸네일만 삽입&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function ($, mw) {&lt;br /&gt;
    var thumbCache = {};&lt;br /&gt;
&lt;br /&gt;
    function parseSliceWidth(value) {&lt;br /&gt;
        var parsed = parseInt(value, 10);&lt;br /&gt;
&lt;br /&gt;
        if (!isFinite(parsed) || parsed &amp;lt; 120) {&lt;br /&gt;
            return 300;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parsed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getImageSrc(img) {&lt;br /&gt;
        return img ? (img.currentSrc || img.getAttribute(&#039;src&#039;) || img.src || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFileNameFromSrc(src) {&lt;br /&gt;
        var a;&lt;br /&gt;
        var parts;&lt;br /&gt;
        var fileName;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = src;&lt;br /&gt;
&lt;br /&gt;
        parts = (a.pathname || &#039;&#039;).split(&#039;/&#039;).filter(function (part) {&lt;br /&gt;
            return !!part;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!parts.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        fileName = parts.pop();&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * MediaWiki thumb URL 예시:&lt;br /&gt;
         * /images/thumb/a/ab/File.png/1000px-File.png&lt;br /&gt;
         * /images/thumb/a/ab/File.svg/1000px-File.svg.png&lt;br /&gt;
         *&lt;br /&gt;
         * 이 경우 실제 파일명은 마지막 조각이 아니라 그 앞 조각이다.&lt;br /&gt;
         */&lt;br /&gt;
        if (/^\d+px-/.test(fileName) &amp;amp;&amp;amp; parts.length) {&lt;br /&gt;
            fileName = parts.pop();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fileName = fileName.replace(/^\d+px-/, &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            fileName = decodeURIComponent(fileName);&lt;br /&gt;
        } catch (e) {}&lt;br /&gt;
&lt;br /&gt;
        return fileName;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveThumbUrl(img, width, callback) {&lt;br /&gt;
        var src = getImageSrc(img);&lt;br /&gt;
        var fileName = getFileNameFromSrc(src);&lt;br /&gt;
        var cacheKey;&lt;br /&gt;
        var entry;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return;&lt;br /&gt;
&lt;br /&gt;
        if (!fileName || !mw || !mw.loader) {&lt;br /&gt;
            callback(src);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        cacheKey = fileName + &#039;|&#039; + width;&lt;br /&gt;
        entry = thumbCache[cacheKey];&lt;br /&gt;
&lt;br /&gt;
        if (entry) {&lt;br /&gt;
            if (entry.resolved) {&lt;br /&gt;
                callback(entry.url || src);&lt;br /&gt;
            } else {&lt;br /&gt;
                entry.callbacks.push(callback);&lt;br /&gt;
            }&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        entry = {&lt;br /&gt;
            resolved: false,&lt;br /&gt;
            url: &#039;&#039;,&lt;br /&gt;
            callbacks: [callback]&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        thumbCache[cacheKey] = entry;&lt;br /&gt;
&lt;br /&gt;
        function finish(url) {&lt;br /&gt;
            var callbacks = entry.callbacks.slice();&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            entry.resolved = true;&lt;br /&gt;
            entry.url = url || src;&lt;br /&gt;
            entry.callbacks = [];&lt;br /&gt;
&lt;br /&gt;
            for (i = 0; i &amp;lt; callbacks.length; i++) {&lt;br /&gt;
                callbacks[i](entry.url);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using(&#039;mediawiki.api&#039;).done(function () {&lt;br /&gt;
            var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + fileName,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url&#039;,&lt;br /&gt;
                iiurlwidth: width,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).done(function (data) {&lt;br /&gt;
                var page;&lt;br /&gt;
                var info;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    data &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages.length&lt;br /&gt;
                ) {&lt;br /&gt;
                    page = data.query.pages[0];&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        page &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo.length&lt;br /&gt;
                    ) {&lt;br /&gt;
                        info = page.imageinfo[0];&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                finish((info &amp;amp;&amp;amp; (info.thumburl || info.url)) || src);&lt;br /&gt;
            }).fail(function () {&lt;br /&gt;
                finish(src);&lt;br /&gt;
            });&lt;br /&gt;
        }).fail(function () {&lt;br /&gt;
            finish(src);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySliceImages(frame, thumbUrl) {&lt;br /&gt;
        var slices;&lt;br /&gt;
        var i;&lt;br /&gt;
        var img;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !thumbUrl) return;&lt;br /&gt;
&lt;br /&gt;
        slices = frame.querySelectorAll(&#039;.crt-page-monitor-slice&#039;);&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; slices.length; i++) {&lt;br /&gt;
            slices[i].innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            img = document.createElement(&#039;img&#039;);&lt;br /&gt;
            img.className = &#039;crt-page-monitor-slice-img&#039;;&lt;br /&gt;
            img.src = thumbUrl;&lt;br /&gt;
            img.alt = &#039;&#039;;&lt;br /&gt;
            img.decoding = &#039;async&#039;;&lt;br /&gt;
            img.loading = &#039;eager&#039;;&lt;br /&gt;
            img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
            slices[i].appendChild(img);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        frame.setAttribute(&#039;data-crt-slices-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrame(frame) {&lt;br /&gt;
        var baseImg;&lt;br /&gt;
        var width;&lt;br /&gt;
&lt;br /&gt;
        if (!frame) return;&lt;br /&gt;
        if (frame.getAttribute(&#039;data-crt-slices-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        baseImg = frame.querySelector(&#039;.crt-page-monitor-image-base img&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!baseImg) return;&lt;br /&gt;
&lt;br /&gt;
        width = parseSliceWidth(frame.getAttribute(&#039;data-crt-slice-width&#039;));&lt;br /&gt;
&lt;br /&gt;
        resolveThumbUrl(baseImg, width, function (thumbUrl) {&lt;br /&gt;
            if (!frame || !frame.parentNode) return;&lt;br /&gt;
            applySliceImages(frame, thumbUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrames(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var frames = scope.querySelectorAll(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; frames.length; i++) {&lt;br /&gt;
            initBannerFrame(frames[i]);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initBannerFrames(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initBannerFrames($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(jQuery, window.mw);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Tab System — tab switching UI&lt;br /&gt;
   글리치 플리커 + RGB split + 방향 슬라이드&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function initDocTabs() {&lt;br /&gt;
        var tabBars = document.querySelectorAll(&#039;.doc-tab-bar&#039;);&lt;br /&gt;
        if (!tabBars.length) return;&lt;br /&gt;
&lt;br /&gt;
        tabBars.forEach(function (bar) {&lt;br /&gt;
            if (bar.getAttribute(&#039;data-tabs-init&#039;)) return;&lt;br /&gt;
            bar.setAttribute(&#039;data-tabs-init&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            var tabs = Array.from(bar.querySelectorAll(&#039;.doc-tab&#039;));&lt;br /&gt;
            if (!tabs.length) return;&lt;br /&gt;
&lt;br /&gt;
            var panel = bar.closest(&#039;.doc-panel&#039;);&lt;br /&gt;
            var display = panel ? panel.querySelector(&#039;.doc-display&#039;) : null;&lt;br /&gt;
            if (!display) display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
            if (!display) return;&lt;br /&gt;
&lt;br /&gt;
            tabs.forEach(function (tab, i) {&lt;br /&gt;
                tab.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
                    var currentIdx = tabs.findIndex(function (t) {&lt;br /&gt;
                        return t.classList.contains(&#039;active&#039;);&lt;br /&gt;
                    });&lt;br /&gt;
                    if (currentIdx === i) return;&lt;br /&gt;
                    switchTab(tabs, display, i, i &amp;gt; currentIdx ? 1 : -1);&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            var initIdx = tabs.findIndex(function (t) { return t.classList.contains(&#039;active&#039;); });&lt;br /&gt;
            if (initIdx !== -1) {&lt;br /&gt;
                var initRef = tabs[initIdx].dataset.ref;&lt;br /&gt;
                var initEl = initRef ? document.getElementById(initRef) : null;&lt;br /&gt;
                display.innerHTML = initEl ? initEl.innerHTML : (tabs[initIdx].dataset.content || &#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var isAnimating = false;&lt;br /&gt;
&lt;br /&gt;
    function switchTab(tabs, display, nextIdx, dir) {&lt;br /&gt;
        if (isAnimating) return;&lt;br /&gt;
        isAnimating = true;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (t) { t.classList.remove(&#039;active&#039;); });&lt;br /&gt;
        tabs[nextIdx].classList.add(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
        var ref = tabs[nextIdx].dataset.ref;&lt;br /&gt;
        var nextContent;&lt;br /&gt;
        if (ref) {&lt;br /&gt;
            var refEl = document.getElementById(ref);&lt;br /&gt;
            nextContent = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            nextContent = tabs[nextIdx].dataset.content || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        glitchOut(display, dir, function () {&lt;br /&gt;
            display.innerHTML = nextContent;&lt;br /&gt;
            glitchIn(display, dir, function () {&lt;br /&gt;
                isAnimating = false;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchOut(el, dir, cb) {&lt;br /&gt;
        var duration = 160;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var slideX = dir * 16;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = p * p;&lt;br /&gt;
&lt;br /&gt;
            var tx = slideX * ease;&lt;br /&gt;
            var skew = dir * ease * 1.0;&lt;br /&gt;
            var opacity = 1 - ease;&lt;br /&gt;
            var rgb = ease * 5;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.75)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.65)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + (1 + ease * 0.25) + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.opacity = &#039;0&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchIn(el, dir, cb) {&lt;br /&gt;
        var duration = 200;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var startX = -dir * 16;&lt;br /&gt;
&lt;br /&gt;
        el.style.transform = &#039;translateX(&#039; + startX + &#039;px) skewX(&#039; + (-dir * 1.0) + &#039;deg)&#039;;&lt;br /&gt;
        el.style.opacity = &#039;0&#039;;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = 1 - Math.pow(1 - p, 3);&lt;br /&gt;
&lt;br /&gt;
            var tx = startX * (1 - ease);&lt;br /&gt;
            var skew = -dir * 1.0 * (1 - ease);&lt;br /&gt;
            var opacity = ease;&lt;br /&gt;
            var rgb = (1 - ease) * 3;&lt;br /&gt;
            var brightness = 1 + (1 - ease) * 0.35;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.65)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.55)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + brightness + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.transform = &#039;&#039;;&lt;br /&gt;
                el.style.opacity = &#039;&#039;;&lt;br /&gt;
                el.style.filter = &#039;&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initDocTabs);&lt;br /&gt;
} else {&lt;br /&gt;
    initDocTabs();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        initDocTabs();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Section Switch — 좌측 섹션 전환&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;.doc-nav-item[data-section]&#039;, function () {&lt;br /&gt;
    var name = $(this).attr(&#039;data-section&#039;);&lt;br /&gt;
    var display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
    var titleEl = document.getElementById(&#039;doc-center-title&#039;);&lt;br /&gt;
    var tabBar = document.getElementById(&#039;doc-tab-bar-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!display) return;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section]&#039;).removeClass(&#039;active&#039;);&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section=&amp;quot;&#039; + name + &#039;&amp;quot;]&#039;).addClass(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (name === &#039;text&#039;) {&lt;br /&gt;
        if (titleEl) titleEl.textContent = &#039;개요&#039;;&lt;br /&gt;
        if (tabBar) $(tabBar).show();&lt;br /&gt;
        var activeTab = tabBar ? tabBar.querySelector(&#039;.doc-tab.active&#039;) : null;&lt;br /&gt;
        if (!activeTab &amp;amp;&amp;amp; tabBar) activeTab = tabBar.querySelector(&#039;.doc-tab&#039;);&lt;br /&gt;
        if (activeTab) {&lt;br /&gt;
            var ref = activeTab.dataset.ref;&lt;br /&gt;
            var refEl = ref ? document.getElementById(ref) : null;&lt;br /&gt;
            display.innerHTML = refEl ? refEl.innerHTML : (activeTab.dataset.content || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        if (titleEl) titleEl.textContent = name === &#039;factions&#039; ? &#039;세력&#039; : name === &#039;people&#039; ? &#039;인물&#039; : name;&lt;br /&gt;
        if (tabBar) $(tabBar).hide();&lt;br /&gt;
        var refEl = document.getElementById(&#039;doc-content-&#039; + name);&lt;br /&gt;
        display.innerHTML = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   CRT WebGL Renderer — cool-retro-term IBM DOS style&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function createNoiseTexture(gl) {&lt;br /&gt;
        var size = 512;&lt;br /&gt;
        var data = new Uint8Array(size * size * 4);&lt;br /&gt;
        var s = 12345;&lt;br /&gt;
        function rand() {&lt;br /&gt;
            s = (s * 1664525 + 1013904223) &amp;amp; 0xffffffff;&lt;br /&gt;
            return (s &amp;gt;&amp;gt;&amp;gt; 0) / 0xffffffff;&lt;br /&gt;
        }&lt;br /&gt;
        for (var i = 0; i &amp;lt; data.length; i++) {&lt;br /&gt;
            data[i] = (rand() * 255) | 0;&lt;br /&gt;
        }&lt;br /&gt;
        var tex = gl.createTexture();&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, tex);&lt;br /&gt;
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
        return tex;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var VERT = [&lt;br /&gt;
        &#039;attribute vec2 a_pos;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5);&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_pos, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var FRAG = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_tex;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_noise;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_res;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_imgSize;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_noiseScale;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float sum2(vec2 v) { return v.x + v.y; }&#039;,&lt;br /&gt;
        &#039;float min2(vec2 v) { return min(v.x, v.y); }&#039;,&lt;br /&gt;
        &#039;float rgb2grey(vec3 v) { return dot(v, vec3(0.21, 0.72, 0.04)); }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 coverUV(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  float imgAR = u_imgSize.x / u_imgSize.y;&#039;,&lt;br /&gt;
        &#039;  float scrAR = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  float scale = imgAR / scrAR;&#039;,&lt;br /&gt;
        &#039;  float offsetY = (1.0 - scale) * 0.5;&#039;,&lt;br /&gt;
        &#039;  return vec2(uv.x, uv.y * scale + offsetY);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 barrel(vec2 v, vec2 cc, float k) {&#039;,&lt;br /&gt;
        &#039;  float ar = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  vec2 c2 = cc;&#039;,&lt;br /&gt;
        &#039;  if (ar &amp;gt; 1.0) c2.x /= ar; else c2.y *= ar;&#039;,&lt;br /&gt;
        &#039;  float dist = dot(c2, c2) * k;&#039;,&lt;br /&gt;
        &#039;  return v - cc * (1.0 + dist) * dist;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleInitialNoise(float t) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, vec2(fract(t/2048.0), fract(t/1048576.0)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleScreenNoise(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, u_noiseScale * uv);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyRgbShift(vec2 texUV, float shift) {&#039;,&lt;br /&gt;
        &#039;  vec2 d = vec2(shift, 0.0);&#039;,&lt;br /&gt;
        &#039;  vec3 r = texture2D(u_tex, clamp(texUV + d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 c = texture2D(u_tex, texUV).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 l = texture2D(u_tex, clamp(texUV - d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  return vec3(&#039;,&lt;br /&gt;
        &#039;    l.r*0.10 + r.r*0.30 + c.r*0.60,&#039;,&lt;br /&gt;
        &#039;    l.g*0.20 + r.g*0.20 + c.g*0.60,&#039;,&lt;br /&gt;
        &#039;    l.b*0.30 + r.b*0.10 + c.b*0.60&#039;,&lt;br /&gt;
        &#039;  );&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyBloom(vec2 texUV, float strength) {&#039;,&lt;br /&gt;
        &#039;  vec2 px = 2.0 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 acc = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0,  px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0, -px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  return acc / 6.0 * strength;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyScanlines(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
        &#039;  float line = mod(uv.y * u_res.y, 2.0);&#039;,&lt;br /&gt;
        &#039;  vec3 hi = ((1.0 + 0.30) - 0.2 * col) * col;&#039;,&lt;br /&gt;
        &#039;  vec3 lo = ((1.0 - 0.30) + 0.1 * col) * col;&#039;,&lt;br /&gt;
        &#039;  return line &amp;lt; 1.0 ? lo : hi;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
&#039;vec3 applyRasterization(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
&#039;  float t = u_time;&#039;,&lt;br /&gt;
&#039;  vec2 noiseUV = uv + vec2(fract(t * 0.030), fract(t * 0.060));&#039;,&lt;br /&gt;
&#039;  float wobbleX = (texture2D(u_noise, noiseUV * 0.8).r - 0.5) * 0.0018;&#039;,&lt;br /&gt;
&#039;  float wobbleY = (texture2D(u_noise, noiseUV * 0.8 + 0.5).r - 0.5) * 0.0008;&#039;,&lt;br /&gt;
&#039;  vec2 wobbledUV = clamp(uv + vec2(wobbleX, wobbleY), 0.0, 1.0);&#039;,&lt;br /&gt;
&#039;  vec3 wobbled = texture2D(u_tex, wobbledUV).rgb;&#039;,&lt;br /&gt;
&#039;  return mix(col, wobbled, 0.35);&#039;,&lt;br /&gt;
&#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float glowingLine(vec2 uv, float t) {&#039;,&lt;br /&gt;
&#039;  float pos = fract(t * 0.2);&#039;,&lt;br /&gt;
&#039;  float lineY = pos * (u_res.y + 330.0) - 120.0;&#039;,&lt;br /&gt;
        &#039;  float y = uv.y * u_res.y;&#039;,&lt;br /&gt;
        &#039;  return fract(smoothstep(-300.0, 0.0, y - lineY));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 applyHSync(vec2 uv, vec4 noise, float strength) {&#039;,&lt;br /&gt;
        &#039;  float randval = strength - noise.r;&#039;,&lt;br /&gt;
        &#039;  float scale = step(0.0, randval) * randval * strength;&#039;,&lt;br /&gt;
        &#039;  float freq = mix(4.0, 40.0, noise.g);&#039;,&lt;br /&gt;
        &#039;  uv.x += sin((uv.y + u_time * 0.001) * freq) * scale;&#039;,&lt;br /&gt;
        &#039;  return uv;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 cc = vec2(0.5) - v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float curvature = 0.18;&#039;,&lt;br /&gt;
        &#039;  vec2 uv = barrel(v_uv, cc, curvature);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float inScreen = min2(step(vec2(0.0), uv) - step(vec2(1.0), uv));&#039;,&lt;br /&gt;
        &#039;  if (inScreen &amp;lt; 0.5) { gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); return; }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 texUV = clamp(coverUV(uv), 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec4 initNoise = sampleInitialNoise(u_time);&#039;,&lt;br /&gt;
        &#039;  vec4 screenNoise = sampleScreenNoise(uv);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV = applyHSync(texUV, initNoise, 0.006);&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV += (vec2(screenNoise.b, screenNoise.a) - 0.5) * 0.0006;&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec3 col = applyRgbShift(texUV, 0.003);&#039;,&lt;br /&gt;
        &#039;  col += applyBloom(texUV, 0.22);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 bpx = 1.5 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 blurCol = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,   -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,    bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  col = mix(col, blurCol / 8.0, 0.40);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = applyScanlines(uv, col);&#039;,&lt;br /&gt;
        &#039;  col = applyRasterization(texUV, col);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float glow = glowingLine(uv, u_time);&#039;,&lt;br /&gt;
&#039;  col += glow * 0.08 * vec3(0.85, 0.95, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float dist = length(cc);&#039;,&lt;br /&gt;
        &#039;  col += screenNoise.a * 0.07 * (1.0 - dist * 1.3);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float grey = rgb2grey(col);&#039;,&lt;br /&gt;
        &#039;  vec3 phosphor = vec3(0.75, 0.88, 1.0);&#039;,&lt;br /&gt;
        &#039;  col = mix(col, grey * phosphor, 0.35);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 vig = v_uv * (1.0 - v_uv);&#039;,&lt;br /&gt;
        &#039;  col *= pow(vig.x * vig.y * 15.0, 0.25);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col *= 1.0 + (initNoise.g - 0.5) * 0.06;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col += vec3(0.012) * (1.0 - dist) * (1.0 - dist);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = pow(clamp(col, 0.0, 1.0), vec3(0.90));&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  gl_FragColor = vec4(col, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function initCRTCanvas(screen, imgEl) {&lt;br /&gt;
        var existing = screen.querySelector(&#039;.crt-webgl-canvas&#039;);&lt;br /&gt;
        if (existing) existing.remove();&lt;br /&gt;
&lt;br /&gt;
        var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.className = &#039;crt-webgl-canvas&#039;;&lt;br /&gt;
        canvas.style.cssText = &#039;position:absolute;inset:0;width:100%;height:100%;z-index:19;pointer-events:none;display:block;&#039;;&lt;br /&gt;
        screen.appendChild(canvas);&lt;br /&gt;
&lt;br /&gt;
        var gl = canvas.getContext(&#039;webgl&#039;) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
        if (!gl) return;&lt;br /&gt;
&lt;br /&gt;
        function compile(type, src) {&lt;br /&gt;
            var s = gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(s, src);&lt;br /&gt;
            gl.compileShader(s);&lt;br /&gt;
            if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {&lt;br /&gt;
                console.error(&#039;[CRT shader]&#039;, gl.getShaderInfoLog(s));&lt;br /&gt;
            }&lt;br /&gt;
            return s;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var prog = gl.createProgram();&lt;br /&gt;
        gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT));&lt;br /&gt;
        gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FRAG));&lt;br /&gt;
        gl.linkProgram(prog);&lt;br /&gt;
        gl.useProgram(prog);&lt;br /&gt;
&lt;br /&gt;
        var buf = gl.createBuffer();&lt;br /&gt;
        gl.bindBuffer(gl.ARRAY_BUFFER, buf);&lt;br /&gt;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);&lt;br /&gt;
        var aPos = gl.getAttribLocation(prog, &#039;a_pos&#039;);&lt;br /&gt;
        gl.enableVertexAttribArray(aPos);&lt;br /&gt;
        gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
        var uTex     = gl.getUniformLocation(prog, &#039;u_tex&#039;);&lt;br /&gt;
        var uNoise   = gl.getUniformLocation(prog, &#039;u_noise&#039;);&lt;br /&gt;
        var uRes     = gl.getUniformLocation(prog, &#039;u_res&#039;);&lt;br /&gt;
        var uImgSize = gl.getUniformLocation(prog, &#039;u_imgSize&#039;);&lt;br /&gt;
        var uTime    = gl.getUniformLocation(prog, &#039;u_time&#039;);&lt;br /&gt;
        var uNoiseSc = gl.getUniformLocation(prog, &#039;u_noiseScale&#039;);&lt;br /&gt;
&lt;br /&gt;
        var imgTex = gl.createTexture();&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE1);&lt;br /&gt;
        createNoiseTexture(gl);&lt;br /&gt;
&lt;br /&gt;
        var texReady = false;&lt;br /&gt;
        function uploadImg() {&lt;br /&gt;
            if (!imgEl || !imgEl.complete || !imgEl.naturalWidth) return;&lt;br /&gt;
            try {&lt;br /&gt;
                gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
                gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imgEl);&lt;br /&gt;
                texReady = true;&lt;br /&gt;
            } catch(e) { console.error(&#039;[CRT] tex:&#039;, e); }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var lastW = 0, lastH = 0;&lt;br /&gt;
        function resize() {&lt;br /&gt;
            var w = screen.offsetWidth, h = screen.offsetHeight;&lt;br /&gt;
            if (w === lastW &amp;amp;&amp;amp; h === lastH) return;&lt;br /&gt;
            lastW = w; lastH = h;&lt;br /&gt;
            canvas.width = w; canvas.height = h;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var raf;&lt;br /&gt;
        var t0 = performance.now();&lt;br /&gt;
&lt;br /&gt;
        function render() {&lt;br /&gt;
            raf = requestAnimationFrame(render);&lt;br /&gt;
            if (!texReady) { uploadImg(); return; }&lt;br /&gt;
            resize();&lt;br /&gt;
            var t = (performance.now() - t0) / 1000;&lt;br /&gt;
            gl.uniform1i(uTex, 0);&lt;br /&gt;
            gl.uniform1i(uNoise, 1);&lt;br /&gt;
            gl.uniform2f(uRes, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform2f(uImgSize, imgEl.naturalWidth, imgEl.naturalHeight);&lt;br /&gt;
            gl.uniform1f(uTime, t);&lt;br /&gt;
            gl.uniform2f(uNoiseSc, canvas.width / 512.0, canvas.height / 512.0);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) { uploadImg(); }&lt;br /&gt;
        else { imgEl.addEventListener(&#039;load&#039;, uploadImg); }&lt;br /&gt;
&lt;br /&gt;
        render();&lt;br /&gt;
        screen._crtCleanup = function () { cancelAnimationFrame(raf); };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initAllCRTScreens(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        scope.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (screen) {&lt;br /&gt;
            if (screen.getAttribute(&#039;data-crt-webgl&#039;) === &#039;1&#039;) return;&lt;br /&gt;
            screen.setAttribute(&#039;data-crt-webgl&#039;, &#039;1&#039;);&lt;br /&gt;
            var frame = screen.closest(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
            if (!frame) return;&lt;br /&gt;
            var imgEl = frame.querySelector(&#039;.crt-page-monitor-slice-img, .crt-page-monitor-image-base img&#039;);&lt;br /&gt;
            if (imgEl &amp;amp;&amp;amp; imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) {&lt;br /&gt;
                initCRTCanvas(screen, imgEl);&lt;br /&gt;
            } else if (imgEl) {&lt;br /&gt;
                imgEl.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, imgEl); });&lt;br /&gt;
            } else {&lt;br /&gt;
                var obs = new MutationObserver(function () {&lt;br /&gt;
                    var img = frame.querySelector(&#039;.crt-page-monitor-slice-img&#039;);&lt;br /&gt;
                    if (!img) return;&lt;br /&gt;
                    obs.disconnect();&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        initCRTCanvas(screen, img);&lt;br /&gt;
                    } else {&lt;br /&gt;
                        img.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, img); });&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
                obs.observe(frame, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () { initAllCRTScreens(document); });&lt;br /&gt;
&lt;br /&gt;
    if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($c) {&lt;br /&gt;
            document.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (s) {&lt;br /&gt;
                if (s._crtCleanup) s._crtCleanup();&lt;br /&gt;
                s.removeAttribute(&#039;data-crt-webgl&#039;);&lt;br /&gt;
            });&lt;br /&gt;
            initAllCRTScreens($c &amp;amp;&amp;amp; $c[0] ? $c[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Progress System UI&lt;br /&gt;
MediaWiki:Common.js controlled frontend&lt;br /&gt;
========================================= */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUiInitialized) return;&lt;br /&gt;
    window.ProgressSystemWebUiInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    var api = null;&lt;br /&gt;
&lt;br /&gt;
    function withApi(done, fail) {&lt;br /&gt;
        if (api) {&lt;br /&gt;
            done(api);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
            api = new mw.Api();&lt;br /&gt;
            done(api);&lt;br /&gt;
        }, function () {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var inFlightPageIds = new Set();&lt;br /&gt;
    var handledPageIds = new Set();&lt;br /&gt;
    var notificationQueue = [];&lt;br /&gt;
    var notificationActive = false;&lt;br /&gt;
    var summaryRequested = false;&lt;br /&gt;
    var currentSummary = null;&lt;br /&gt;
    var pendingSummary = null;&lt;br /&gt;
    var pendingOptions = null;&lt;br /&gt;
    var visibilityBound = false;&lt;br /&gt;
    var barTimerA = null;&lt;br /&gt;
    var barTimerB = null;&lt;br /&gt;
    var barTimerC = null;&lt;br /&gt;
    var summaryRetryTimer = null;&lt;br /&gt;
    var summaryRetryAttempts = 0;&lt;br /&gt;
&lt;br /&gt;
    function isLoggedIn() {&lt;br /&gt;
        return !!mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPageId() {&lt;br /&gt;
        var id = parseInt(mw.config.get(&#039;wgArticleId&#039;) || 0, 10);&lt;br /&gt;
        return Number.isFinite(id) ? id : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isRewardableClientSide() {&lt;br /&gt;
        if (!isLoggedIn()) return false;&lt;br /&gt;
        if (parseInt(mw.config.get(&#039;wgNamespaceNumber&#039;), 10) !== 0) return false;&lt;br /&gt;
        if (mw.config.get(&#039;wgIsMainPage&#039;)) return false;&lt;br /&gt;
        if (getPageId() &amp;lt;= 0) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPanelHtml() {&lt;br /&gt;
        return &#039;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;progress-panel&amp;quot; class=&amp;quot;profile-progress-block is-syncing&amp;quot; aria-live=&amp;quot;polite&amp;quot; data-progress-state=&amp;quot;syncing&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-title-row&amp;quot; hidden&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-level-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-level-label&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-gain&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-sub-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-xp-next&amp;quot;&amp;gt;SYNCING&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-daily-xp&amp;quot;&amp;gt;TODAY —&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-discovery-row&amp;quot;&amp;gt;DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDividerHtml() {&lt;br /&gt;
        /* 프로필 패널 최신 규칙: 레벨 패널과 버튼 영역 사이에 별도 나눔선은 만들지 않는다. */&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setPanelSync($panel) {&lt;br /&gt;
        if (!$panel || !$panel.length) return;&lt;br /&gt;
&lt;br /&gt;
        $panel.addClass(&#039;is-syncing&#039;).removeClass(&#039;is-max-level&#039;).attr(&#039;data-progress-state&#039;, &#039;syncing&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-title-row&#039;).text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text(&#039;SYNC&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(&#039;— XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(&#039;SYNCING&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-fill&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
        $panel.find(&#039;.progress-xp-gain&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function placePanel($panel) {&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $userBox = $right.children(&#039;.clbi-right-box&#039;).first();&lt;br /&gt;
        if (!$userBox.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $buttonArea = $userBox.children(&#039;.clbi-right-content&#039;).first();&lt;br /&gt;
        var $oldFallback = $panel.closest(&#039;.progress-panel-fallback&#039;);&lt;br /&gt;
&lt;br /&gt;
        if ($buttonArea.length) {&lt;br /&gt;
            var $divider = $(&#039;#profile-progress-divider&#039;);&lt;br /&gt;
&lt;br /&gt;
            $panel.insertBefore($buttonArea);&lt;br /&gt;
&lt;br /&gt;
            if (!$divider.length) {&lt;br /&gt;
                $divider = $(getDividerHtml());&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $divider.insertAfter($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;#profile-progress-divider&#039;).remove();&lt;br /&gt;
            $userBox.append($panel);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if ($oldFallback.length &amp;amp;&amp;amp; !$oldFallback.find(&#039;#progress-panel&#039;).length) {&lt;br /&gt;
            $oldFallback.remove();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensurePanel() {&lt;br /&gt;
        if (!isLoggedIn()) return $();&lt;br /&gt;
&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return $();&lt;br /&gt;
&lt;br /&gt;
        var $panel = $(&#039;#progress-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            $panel = $(getPanelHtml());&lt;br /&gt;
            if (!placePanel($panel)) return $();&lt;br /&gt;
            setPanelSync($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $panel.addClass(&#039;profile-progress-block&#039;);&lt;br /&gt;
            placePanel($panel);&lt;br /&gt;
&lt;br /&gt;
            if (!currentSummary &amp;amp;&amp;amp; $panel.attr(&#039;data-progress-state&#039;) !== &#039;syncing&#039;) {&lt;br /&gt;
                setPanelSync($panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $(&#039;#progress-panel&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clampPercent(value) {&lt;br /&gt;
        return Math.max(0, Math.min(100, value || 0));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasXpNotification(items) {&lt;br /&gt;
        if (!items || !items.length) return false;&lt;br /&gt;
        return items.some(function (item) {&lt;br /&gt;
            return item &amp;amp;&amp;amp; item.type === &#039;xp&#039; &amp;amp;&amp;amp; parseInt(item.amount || 0, 10) &amp;gt; 0;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearBarTimers() {&lt;br /&gt;
        [barTimerA, barTimerB, barTimerC].forEach(function (timer) {&lt;br /&gt;
            if (timer) clearTimeout(timer);&lt;br /&gt;
        });&lt;br /&gt;
        barTimerA = null;&lt;br /&gt;
        barTimerB = null;&lt;br /&gt;
        barTimerC = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBarInstant($fill, $gain, percent) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
        percent = clampPercent(percent);&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: percent + &#039;%&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;none&#039;, left: &#039;0%&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
        $fill.css({ transition: &#039;&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function animateGain($fill, $gain, fromPercent, toPercent, levelChanged) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
&lt;br /&gt;
        fromPercent = clampPercent(fromPercent);&lt;br /&gt;
        toPercent = clampPercent(toPercent);&lt;br /&gt;
&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: fromPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
        if (levelChanged) {&lt;br /&gt;
            var firstDelta = Math.max(0, 100 - fromPercent);&lt;br /&gt;
&lt;br /&gt;
            $gain.css({&lt;br /&gt;
                transition: &#039;none&#039;,&lt;br /&gt;
                opacity: firstDelta &amp;gt; 0 ? 1 : 0,&lt;br /&gt;
                left: fromPercent + &#039;%&#039;,&lt;br /&gt;
                width: firstDelta + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
            barTimerA = setTimeout(function () {&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 540ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: &#039;100%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 260);&lt;br /&gt;
&lt;br /&gt;
            barTimerB = setTimeout(function () {&lt;br /&gt;
                $fill.css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
                $gain.css({ transition: &#039;none&#039;, opacity: toPercent &amp;gt; 0 ? 1 : 0, left: &#039;0%&#039;, width: toPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
                if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 460ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: toPercent + &#039;%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 860);&lt;br /&gt;
&lt;br /&gt;
            barTimerC = setTimeout(function () {&lt;br /&gt;
                $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
            }, 1380);&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var delta = Math.max(0, toPercent - fromPercent);&lt;br /&gt;
&lt;br /&gt;
        if (delta &amp;lt;= 0.15) {&lt;br /&gt;
            setBarInstant($fill, $gain, toPercent);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $gain.css({&lt;br /&gt;
            transition: &#039;none&#039;,&lt;br /&gt;
            opacity: 1,&lt;br /&gt;
            left: fromPercent + &#039;%&#039;,&lt;br /&gt;
            width: delta + &#039;%&#039;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
        barTimerA = setTimeout(function () {&lt;br /&gt;
            $fill.css({&lt;br /&gt;
                transition: &#039;width 560ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                width: toPercent + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }, 260);&lt;br /&gt;
&lt;br /&gt;
        barTimerB = setTimeout(function () {&lt;br /&gt;
            $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
        }, 940);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updatePanel(summary, options) {&lt;br /&gt;
        if (!summary) return;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
&lt;br /&gt;
        var $panel = ensurePanel();&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            pendingSummary = $.extend({}, summary);&lt;br /&gt;
            pendingOptions = $.extend({}, options);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var level = summary.level || 1;&lt;br /&gt;
        var totalXp = summary.totalXp || 0;&lt;br /&gt;
        var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
        var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
        var percent = clampPercent(summary.progressPercent);&lt;br /&gt;
        var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
        var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
        var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
        var title = summary.equippedTitle || summary.title || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $panel.removeClass(&#039;is-syncing&#039;).toggleClass(&#039;is-max-level&#039;, isMaxLevel).attr(&#039;data-progress-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text((isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(totalXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;));&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY &#039; + dailyXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED &#039; + discoveries);&lt;br /&gt;
&lt;br /&gt;
        var $title = $panel.find(&#039;.progress-title-row&#039;);&lt;br /&gt;
        if (title) {&lt;br /&gt;
            $title.text(title).prop(&#039;hidden&#039;, false);&lt;br /&gt;
        } else {&lt;br /&gt;
            $title.text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $fill = $panel.find(&#039;.progress-xp-fill&#039;);&lt;br /&gt;
        var $gain = $panel.find(&#039;.progress-xp-gain&#039;);&lt;br /&gt;
        var animate = !!options.animateGain &amp;amp;&amp;amp; currentSummary &amp;amp;&amp;amp; totalXp &amp;gt; (currentSummary.totalXp || 0);&lt;br /&gt;
&lt;br /&gt;
        if (animate) {&lt;br /&gt;
            animateGain(&lt;br /&gt;
                $fill,&lt;br /&gt;
                $gain,&lt;br /&gt;
                clampPercent(currentSummary.progressPercent),&lt;br /&gt;
                percent,&lt;br /&gt;
                level !== (currentSummary.level || 1)&lt;br /&gt;
            );&lt;br /&gt;
        } else {&lt;br /&gt;
            setBarInstant($fill, $gain, percent);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        currentSummary = $.extend({}, summary);&lt;br /&gt;
        pendingSummary = null;&lt;br /&gt;
        pendingOptions = null;&lt;br /&gt;
        if (summaryRetryTimer) {&lt;br /&gt;
            clearTimeout(summaryRetryTimer);&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
        }&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearSummaryRetry() {&lt;br /&gt;
        if (summaryRetryTimer) clearTimeout(summaryRetryTimer);&lt;br /&gt;
        summaryRetryTimer = null;&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleSummaryRetry(delay) {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRetryTimer) return;&lt;br /&gt;
        if (summaryRetryAttempts &amp;gt;= 12) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRetryAttempts += 1;&lt;br /&gt;
        summaryRetryTimer = setTimeout(function () {&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, delay || 1800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSummary() {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRequested) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRequested = true;&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;progress_summary&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
                if (payload &amp;amp;&amp;amp; payload.available &amp;amp;&amp;amp; payload.summary) {&lt;br /&gt;
                    clearSummaryRetry();&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: false });&lt;br /&gt;
                } else {&lt;br /&gt;
                    scheduleSummaryRetry(2200);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                scheduleSummaryRetry(2200);&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                summaryRequested = false;&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            summaryRequested = false;&lt;br /&gt;
            scheduleSummaryRetry(2200);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queueNotifications(items) {&lt;br /&gt;
        if (!items || !items.length) return;&lt;br /&gt;
&lt;br /&gt;
        items.forEach(function (item) {&lt;br /&gt;
            if (!item) return;&lt;br /&gt;
            notificationQueue.push(item);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        showNextNotification();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function notificationText(item) {&lt;br /&gt;
        if (item.type === &#039;xp&#039;) {&lt;br /&gt;
            return &#039;+&#039; + (item.amount || 0) + &#039; XP · &#039; + (item.label || &#039;문서 열람&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;achievement&#039;) {&lt;br /&gt;
            var xp = item.amount ? &#039; · +&#039; + item.amount + &#039; XP&#039; : &#039;&#039;;&lt;br /&gt;
            return &#039;업적 달성 · &#039; + (item.label || &#039;새 업적&#039;) + xp;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;level&#039;) {&lt;br /&gt;
            return item.label || &#039;레벨 상승&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return item.label || &#039;보상 획득&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showNextNotification() {&lt;br /&gt;
        if (notificationActive) return;&lt;br /&gt;
        if (!notificationQueue.length) return;&lt;br /&gt;
&lt;br /&gt;
        notificationActive = true;&lt;br /&gt;
        var item = notificationQueue.shift();&lt;br /&gt;
        var $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$root.length) {&lt;br /&gt;
            $(&#039;body&#039;).append(&#039;&amp;lt;div id=&amp;quot;progress-toast-root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $toast = $(&#039;&amp;lt;div class=&amp;quot;progress-toast&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        $toast.text(notificationText(item));&lt;br /&gt;
        $root.append($toast);&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            $toast.addClass(&#039;is-visible&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            $toast.removeClass(&#039;is-visible&#039;);&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                $toast.remove();&lt;br /&gt;
                notificationActive = false;&lt;br /&gt;
                showNextNotification();&lt;br /&gt;
            }, 220);&lt;br /&gt;
        }, 2600);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPendingSummaryIfPossible() {&lt;br /&gt;
        if (!pendingSummary) return;&lt;br /&gt;
        updatePanel(pendingSummary, pendingOptions || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handlePageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (!isRewardableClientSide()) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var pageId = getPageId();&lt;br /&gt;
        if (handledPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (inFlightPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inFlightPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;progress_view&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2,&lt;br /&gt;
                errorformat: &#039;plaintext&#039;,&lt;br /&gt;
                pageid: pageId&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_view;&lt;br /&gt;
                if (!payload) return;&lt;br /&gt;
&lt;br /&gt;
                handledPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
                var animate = hasXpNotification(payload.notifications);&lt;br /&gt;
&lt;br /&gt;
                if (payload.summary) {&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: animate });&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (payload.notifications &amp;amp;&amp;amp; payload.notifications.length) {&lt;br /&gt;
                    queueNotifications(payload.notifications);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                inFlightPageIds.delete(pageId);&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            inFlightPageIds.delete(pageId);&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindVisibilitySync() {&lt;br /&gt;
        if (visibilityBound) return;&lt;br /&gt;
        visibilityBound = true;&lt;br /&gt;
&lt;br /&gt;
        document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
            if (document.visibilityState === &#039;visible&#039;) {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bootProgressSystem(reason) {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (isRewardableClientSide()) {&lt;br /&gt;
            handlePageView();&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 350);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 1500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleSpaPageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                handlePageView();&lt;br /&gt;
            }, 80);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySummary(summary, options) {&lt;br /&gt;
        updatePanel(summary, options || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.ProgressSystemWebUi = {&lt;br /&gt;
        boot: bootProgressSystem,&lt;br /&gt;
        requestSummary: requestSummary,&lt;br /&gt;
        applySummary: applySummary,&lt;br /&gt;
        handlePageView: handlePageView,&lt;br /&gt;
        handleSpaPageView: handleSpaPageView,&lt;br /&gt;
        ensurePanel: ensurePanel&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        bindVisibilitySync();&lt;br /&gt;
        bootProgressSystem(&#039;documentReady&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* CLBI Nations / Historical Events year tabs&lt;br /&gt;
 * Mirrors the country information panel model:&lt;br /&gt;
 * active tab uses .is-active/aria-selected and inactive pages use hidden.&lt;br /&gt;
 */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function activateClbiNationsHistoryYear(panel, targetYear) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var pages;&lt;br /&gt;
&lt;br /&gt;
        if (!panel || !targetYear) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        pages = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-page[data-year-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
        if (!tabs.length || !pages.length) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (tab) {&lt;br /&gt;
            var active = tab.getAttribute(&#039;data-year&#039;) === targetYear;&lt;br /&gt;
            tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
            tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        pages.forEach(function (page) {&lt;br /&gt;
            var active = page.getAttribute(&#039;data-year-panel&#039;) === targetYear;&lt;br /&gt;
            page.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
            if (active) {&lt;br /&gt;
                page.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
            } else {&lt;br /&gt;
                page.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveClbiNationsHistoryYear(panel, direction) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var activeIndex;&lt;br /&gt;
        var nextIndex;&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!panel) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
        activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
            return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
        nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
        target = tabs[nextIndex].getAttribute(&#039;data-year&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (activateClbiNationsHistoryYear(panel, target)) {&lt;br /&gt;
            tabs[nextIndex].focus();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initClbiNationsHistoryYearTabs(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var panels = scope.querySelectorAll(&#039;.clbi-nations-history-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(panels, function (panel) {&lt;br /&gt;
            if (panel.getAttribute(&#039;data-clbi-history-tabs-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
            panel.setAttribute(&#039;data-clbi-history-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (activateClbiNationsHistoryYear(panel, tab.getAttribute(&#039;data-year&#039;))) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
                var handled = false;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsHistoryYear(panel, -1);&lt;br /&gt;
                else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsHistoryYear(panel, 1);&lt;br /&gt;
                else if (event.key === &#039;Home&#039;) handled = activateClbiNationsHistoryYear(panel, (panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;) || {}).getAttribute &amp;amp;&amp;amp; panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;).getAttribute(&#039;data-year&#039;));&lt;br /&gt;
                else if (event.key === &#039;End&#039;) {&lt;br /&gt;
                    var tabs = panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;);&lt;br /&gt;
                    var last = tabs[tabs.length - 1];&lt;br /&gt;
                    handled = last ? activateClbiNationsHistoryYear(panel, last.getAttribute(&#039;data-year&#039;)) : false;&lt;br /&gt;
                    if (handled) last.focus();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (handled) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                    event.stopPropagation();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.initClbiNationsHistoryYearTabs = initClbiNationsHistoryYearTabs;&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initClbiNationsHistoryYearTabs(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initClbiNationsHistoryYearTabs($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Decoration runtime renderer&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var REGISTRY_TITLE = &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
    var RENDERED_ATTR = &#039;data-wiki-decoration-rendered&#039;;&lt;br /&gt;
    var HOST_ATTR = &#039;data-wiki-decoration-host&#039;;&lt;br /&gt;
    var MAIN_PAGE_PLACEMENT = &#039;main-body-well&#039;;&lt;br /&gt;
    var MAIN_PAGE_TARGET = &#039;.main-portal .main-body-well&#039;;&lt;br /&gt;
    var runtimeToken = 0;&lt;br /&gt;
    var lastRegistry = null;&lt;br /&gt;
    var lastRenderedPageKey = &#039;&#039;;&lt;br /&gt;
    var scheduledRender = 0;&lt;br /&gt;
    var nationsPlacementObserver = null;&lt;br /&gt;
    var observedNationsStack = null;&lt;br /&gt;
    var mainPagePlacementObserver = null;&lt;br /&gt;
    var mainPagePlacementMutationObserver = null;&lt;br /&gt;
    var observedMainPagePortal = null;&lt;br /&gt;
    var scheduledMainPageRebase = 0;&lt;br /&gt;
    var pixelAssetCache = {};&lt;br /&gt;
    var pixelCanvasCache = {};&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentPageKey() {&lt;br /&gt;
        var raw = mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        return normalizePageName(raw) || raw || &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureMainPageBodyWell() {&lt;br /&gt;
        var portal;&lt;br /&gt;
        var topMount;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var well;&lt;br /&gt;
        var manifesto;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
        portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        if (!portal) return null;&lt;br /&gt;
&lt;br /&gt;
        topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) {&lt;br /&gt;
                    topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                } else {&lt;br /&gt;
                    topMount.parentNode.appendChild(panel);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) {&lt;br /&gt;
            well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return well;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function cssAttrEscape(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).replace(/\\/g, &#039;\\\\&#039;).replace(/&amp;quot;/g, &#039;\\&amp;quot;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEra() {&lt;br /&gt;
        var content = document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var globe;&lt;br /&gt;
&lt;br /&gt;
        if (content) return content.getAttribute(&#039;data-era-content&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.querySelector(&#039;.clbi-nations-era-title-plate.is-active[data-era]&#039;);&lt;br /&gt;
        if (title) return title.getAttribute(&#039;data-era&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe]&#039;);&lt;br /&gt;
        if (globe) {&lt;br /&gt;
            return globe.getAttribute(&#039;data-current-era&#039;) || globe.getAttribute(&#039;data-nations-current-era&#039;) || globe.getAttribute(&#039;data-era-year&#039;) || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEraPanel() {&lt;br /&gt;
        var era = getActiveNationsEra();&lt;br /&gt;
        var selector;&lt;br /&gt;
        if (!era) return document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        selector = &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;]&#039;;&lt;br /&gt;
        return document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsContinent() {&lt;br /&gt;
        var eraPanel = getActiveNationsEraPanel();&lt;br /&gt;
        var root = eraPanel || document;&lt;br /&gt;
        var tab = root.querySelector(&#039;.clbi-nations-tabpanel-tab.is-active[data-continent], .clbi-nations-tabpanel-tab[aria-selected=&amp;quot;true&amp;quot;][data-continent]&#039;);&lt;br /&gt;
        var panel;&lt;br /&gt;
&lt;br /&gt;
        if (tab) return tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        panel = root.querySelector(&#039;.clbi-nations-tabpanel-continent.is-active[data-continent-panel]&#039;);&lt;br /&gt;
        if (panel) return panel.getAttribute(&#039;data-continent-panel&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationNationsBodySelector(era) {&lt;br /&gt;
        if (era) {&lt;br /&gt;
            return &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;] .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden]) .clbi-nations-tabpanel-body, .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Decoration semantic placement resolver&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    장식 저장 데이터의 placement 값은 &amp;quot;사용자가 고른 의미상 위치&amp;quot;를 나타낸다.&lt;br /&gt;
    resolver는 그 의미값을 실제 DOM 부착 위치와 표시 조건으로 번역한다.&lt;br /&gt;
&lt;br /&gt;
    예: 시대 문서에서 사용자가 1950년 / 아메리카를 지정하면 의미상 scope는&lt;br /&gt;
    그 조합이지만, 이미지를 붙일 기준면은 대륙 패널 자체가 아니라&lt;br /&gt;
    .clbi-nations-tabpanel-body이다. 따라서 placement=nations-continent-body는&lt;br /&gt;
    .clbi-nations-tabpanel-body에 이미지를 붙이고, 현재 활성 연도와 대륙이 저장값과&lt;br /&gt;
    일치할 때만 렌더링한다.&lt;br /&gt;
&lt;br /&gt;
    유지보수 규칙:&lt;br /&gt;
    - 새 조합형 문서가 생기면 entry.target을 매번 특수하게 저장하지 말고 placement를 추가한다.&lt;br /&gt;
    - DevTools.js 에디터 미리보기와 Common.js 런타임 렌더러의 resolver는 같은 의미를 가져야 한다.&lt;br /&gt;
    - target은 물리적 기준면, era/continent 같은 필드는 표시 조건으로 다룬다.&lt;br /&gt;
    */&lt;br /&gt;
    function shouldUseNationsBodyPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var target = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!document.querySelector(&#039;.clbi-nations-panel-stack&#039;)) return false;&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) return true;&lt;br /&gt;
        if (era || continent) return true;&lt;br /&gt;
        if (target.indexOf(&#039;clbi-nations-tabpanel-continent&#039;) !== -1) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveDecorationPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() || &#039;.liberty-content-main&#039;;&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!placement &amp;amp;&amp;amp; shouldUseNationsBodyPlacement(entry)) {&lt;br /&gt;
            placement = &#039;nations-continent-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;boot-gate&#039; || placement === &#039;loading-screen&#039;) {&lt;br /&gt;
            selector = &#039;#boot-gate-screen .boot-gate-decoration-layer, #boot-gate-screen&#039;;&lt;br /&gt;
            target = document.querySelector(selector);&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: !!document.getElementById(&#039;boot-gate-screen&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) {&lt;br /&gt;
            selector = getDecorationNationsBodySelector(era);&lt;br /&gt;
            target = document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-tabpanel-body&#039;) || document.querySelector(String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim());&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: (!era || era === getActiveNationsEra()) &amp;amp;&amp;amp; (!continent || continent === getActiveNationsContinent())&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문의 일반 장식은 저장된 target과 관계없이 본문 우물 안에서 렌더링한다.&lt;br /&gt;
        우물이 overflow:hidden이므로 #1d1d1d 프레임 밖으로 나갈 수 없다.&lt;br /&gt;
        boot/loading 및 국가 패널 전용 placement는 위 분기에서 기존 동작을 유지한다.&lt;br /&gt;
        */&lt;br /&gt;
        var mainPageWell = ensureMainPageBodyWell();&lt;br /&gt;
        if (mainPageWell) {&lt;br /&gt;
            return {&lt;br /&gt;
                placement: MAIN_PAGE_PLACEMENT,&lt;br /&gt;
                target: mainPageWell,&lt;br /&gt;
                targetSelector: MAIN_PAGE_TARGET,&lt;br /&gt;
                visible: true&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            placement: placement,&lt;br /&gt;
            target: document.querySelector(selector),&lt;br /&gt;
            targetSelector: selector,&lt;br /&gt;
            visible: true&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeNumber(value, fallback) {&lt;br /&gt;
        var n = parseFloat(value);&lt;br /&gt;
        return Number.isFinite(n) ? n : fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBool(value, fallback) {&lt;br /&gt;
        if (value === true || value === &#039;true&#039; || value === &#039;1&#039; || value === 1) return true;&lt;br /&gt;
        if (value === false || value === &#039;false&#039; || value === &#039;0&#039; || value === 0) return false;&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeSrc(src) {&lt;br /&gt;
        src = String(src || &#039;&#039;).trim();&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(src) || src.charAt(0) === &#039;/&#039; || src.indexOf(&#039;data:&#039;) === 0 || src.indexOf(&#039;blob:&#039;) === 0) return src;&lt;br /&gt;
        return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(src);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function normalizeAssetType(entry) {&lt;br /&gt;
        var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var ref = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (type === &#039;clbi-pixel-json&#039; || type === &#039;pixel-rle&#039; || type === &#039;pixel-json&#039;) return &#039;pixel-json&#039;;&lt;br /&gt;
        if (!type &amp;amp;&amp;amp; /\.json(?:[?#].*)?$/i.test(ref)) return &#039;pixel-json&#039;;&lt;br /&gt;
        return type || &#039;image&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPixelJsonDecoration(entry) {&lt;br /&gt;
        return normalizeAssetType(entry) === &#039;pixel-json&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPixelJsonRef(entry) {&lt;br /&gt;
        return String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelJsonUrl(ref) {&lt;br /&gt;
        ref = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        if (!ref) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(ref) || ref.charAt(0) === &#039;/&#039; || ref.indexOf(&#039;data:&#039;) === 0 || ref.indexOf(&#039;blob:&#039;) === 0) return ref;&lt;br /&gt;
        if (/^(?:file|파일):/i.test(ref)) return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(ref.replace(/^(?:file|파일):/i, &#039;&#039;));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            if (ref.indexOf(&#039;:&#039;) !== -1) {&lt;br /&gt;
                return mw.util.getUrl(ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
            }&lt;br /&gt;
            return mw.util.getUrl(&#039;MediaWiki:&#039; + ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return ref;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function parsePixelColor(value) {&lt;br /&gt;
        var text;&lt;br /&gt;
        var m;&lt;br /&gt;
        if (Array.isArray(value)) {&lt;br /&gt;
            return [&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[0]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[1]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[2]) || 0)),&lt;br /&gt;
                value.length &amp;gt; 3 ? Math.max(0, Math.min(255, Number(value[3]) || 0)) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        m = text.match(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i);&lt;br /&gt;
        if (!m) return [0, 0, 0, 0];&lt;br /&gt;
        text = m[1];&lt;br /&gt;
        if (text.length === 3 || text.length === 4) {&lt;br /&gt;
            return [&lt;br /&gt;
                parseInt(text.charAt(0) + text.charAt(0), 16),&lt;br /&gt;
                parseInt(text.charAt(1) + text.charAt(1), 16),&lt;br /&gt;
                parseInt(text.charAt(2) + text.charAt(2), 16),&lt;br /&gt;
                text.length === 4 ? parseInt(text.charAt(3) + text.charAt(3), 16) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        return [&lt;br /&gt;
            parseInt(text.slice(0, 2), 16),&lt;br /&gt;
            parseInt(text.slice(2, 4), 16),&lt;br /&gt;
            parseInt(text.slice(4, 6), 16),&lt;br /&gt;
            text.length === 8 ? parseInt(text.slice(6, 8), 16) : 255&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function decodePixelRle36(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var parts;&lt;br /&gt;
        var runs = [];&lt;br /&gt;
        var i;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
&lt;br /&gt;
        if (!text) return runs;&lt;br /&gt;
        parts = text.split(&#039;,&#039;);&lt;br /&gt;
        for (i = 0; i + 3 &amp;lt; parts.length; i += 4) {&lt;br /&gt;
            x = parseInt(parts[i], 36);&lt;br /&gt;
            y = parseInt(parts[i + 1], 36);&lt;br /&gt;
            len = parseInt(parts[i + 2], 36);&lt;br /&gt;
            colorIndex = parseInt(parts[i + 3], 36);&lt;br /&gt;
            if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(len) || !Number.isFinite(colorIndex)) continue;&lt;br /&gt;
            runs.push([x, y, len, colorIndex]);&lt;br /&gt;
        }&lt;br /&gt;
        return runs;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelAsset(doc) {&lt;br /&gt;
        var encoding = String(doc &amp;amp;&amp;amp; (doc.encoding || doc.e) || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var width = Math.round(Number(doc &amp;amp;&amp;amp; (doc.width || doc.w)) || 0);&lt;br /&gt;
        var height = Math.round(Number(doc &amp;amp;&amp;amp; (doc.height || doc.h)) || 0);&lt;br /&gt;
        var paletteSource = Array.isArray(doc &amp;amp;&amp;amp; doc.palette) ? doc.palette : (Array.isArray(doc &amp;amp;&amp;amp; doc.p) ? doc.p : []);&lt;br /&gt;
        var palette = paletteSource.map(parsePixelColor);&lt;br /&gt;
        var runs;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        CLBI Pixel Forge v0.2.3 compact format:&lt;br /&gt;
        - MediaWiki 단일 문서 크기 제한을 피하기 위해 사람이 읽기 쉬운 [[x,y,len,c], ...] 배열 대신&lt;br /&gt;
          base36 토큰 문자열을 쓴다.&lt;br /&gt;
        - 형식은 x,y,len,colorIndex를 4개 토큰 단위로 반복한 rle36이다.&lt;br /&gt;
        - 복원 후 좌표계는 기존 runs 배열과 완전히 같으며, 최종 CSS px에 1:1로 찍는다.&lt;br /&gt;
        */&lt;br /&gt;
        if ((encoding === &#039;rle36&#039; || encoding === &#039;clbi-rle36&#039;) &amp;amp;&amp;amp; typeof (doc &amp;amp;&amp;amp; doc.r) === &#039;string&#039;) {&lt;br /&gt;
            runs = decodePixelRle36(doc.r);&lt;br /&gt;
        } else {&lt;br /&gt;
            runs = Array.isArray(doc &amp;amp;&amp;amp; doc.runs) ? doc.runs : [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!width || !height || width &amp;lt; 1 || height &amp;lt; 1) throw new Error(&#039;invalid pixel decoration size&#039;);&lt;br /&gt;
        if (width &amp;gt; 8192 || height &amp;gt; 8192) throw new Error(&#039;pixel decoration too large&#039;);&lt;br /&gt;
        return {&lt;br /&gt;
            type: String(doc &amp;amp;&amp;amp; (doc.type || doc.t) || &#039;clbi-pixel-decoration&#039;),&lt;br /&gt;
            version: Number(doc &amp;amp;&amp;amp; (doc.version || doc.v)) || 1,&lt;br /&gt;
            encoding: encoding || &#039;runs&#039;,&lt;br /&gt;
            width: width,&lt;br /&gt;
            height: height,&lt;br /&gt;
            palette: palette,&lt;br /&gt;
            runs: runs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchPixelAsset(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelAssetCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {&lt;br /&gt;
            promise: fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
                .then(function (response) {&lt;br /&gt;
                    if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                    return response.json();&lt;br /&gt;
                })&lt;br /&gt;
                .then(normalizePixelAsset)&lt;br /&gt;
        };&lt;br /&gt;
        pixelAssetCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawPixelAssetToCanvas(canvas, asset) {&lt;br /&gt;
        var ctx;&lt;br /&gt;
        var image;&lt;br /&gt;
        var data;&lt;br /&gt;
        var i;&lt;br /&gt;
        var run;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var color;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
        var p;&lt;br /&gt;
        var k;&lt;br /&gt;
&lt;br /&gt;
        canvas.width = asset.width;&lt;br /&gt;
        canvas.height = asset.height;&lt;br /&gt;
        ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        ctx.imageSmoothingEnabled = false;&lt;br /&gt;
        image = ctx.createImageData(asset.width, asset.height);&lt;br /&gt;
        data = image.data;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; asset.runs.length; i += 1) {&lt;br /&gt;
            run = asset.runs[i];&lt;br /&gt;
            if (!Array.isArray(run) || run.length &amp;lt; 4) continue;&lt;br /&gt;
            x = Math.round(Number(run[0]) || 0);&lt;br /&gt;
            y = Math.round(Number(run[1]) || 0);&lt;br /&gt;
            len = Math.round(Number(run[2]) || 0);&lt;br /&gt;
            colorIndex = Math.round(Number(run[3]) || 0);&lt;br /&gt;
            color = asset.palette[colorIndex];&lt;br /&gt;
            if (!color || len &amp;lt;= 0 || y &amp;lt; 0 || y &amp;gt;= asset.height || x &amp;gt;= asset.width) continue;&lt;br /&gt;
            if (x &amp;lt; 0) {&lt;br /&gt;
                len += x;&lt;br /&gt;
                x = 0;&lt;br /&gt;
            }&lt;br /&gt;
            len = Math.min(len, asset.width - x);&lt;br /&gt;
            for (k = 0; k &amp;lt; len; k += 1) {&lt;br /&gt;
                p = ((y * asset.width) + x + k) * 4;&lt;br /&gt;
                data[p] = color[0];&lt;br /&gt;
                data[p + 1] = color[1];&lt;br /&gt;
                data[p + 2] = color[2];&lt;br /&gt;
                data[p + 3] = color[3];&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preparePixelCanvas(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelCanvasCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {};&lt;br /&gt;
        cached.promise = fetchPixelAsset(ref).then(function (asset) {&lt;br /&gt;
            var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            drawPixelAssetToCanvas(canvas, asset);&lt;br /&gt;
            canvas.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;data-decoration-asset-size&#039;, asset.width + &#039;x&#039; + asset.height);&lt;br /&gt;
            cached.canvas = canvas;&lt;br /&gt;
            cached.width = asset.width;&lt;br /&gt;
            cached.height = asset.height;&lt;br /&gt;
            return canvas;&lt;br /&gt;
        });&lt;br /&gt;
        pixelCanvasCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedPixelCanvasSync(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached = url ? pixelCanvasCache[url] : null;&lt;br /&gt;
        return cached &amp;amp;&amp;amp; cached.canvas ? cached.canvas : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queryDecorationTarget(selector) {&lt;br /&gt;
        selector = String(selector || &#039;&#039;).trim();&lt;br /&gt;
        if (!selector) return null;&lt;br /&gt;
        try {&lt;br /&gt;
            return document.querySelector(selector);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function usesCanonicalMainPageCoordinates(entry, actualTarget) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var declaredTarget;&lt;br /&gt;
&lt;br /&gt;
        if (placement === MAIN_PAGE_PLACEMENT) return true;&lt;br /&gt;
        if (!actualTarget || !selector) return false;&lt;br /&gt;
&lt;br /&gt;
        declaredTarget = queryDecorationTarget(selector);&lt;br /&gt;
        return declaredTarget === actualTarget;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationLocalPosition(entry, actualTarget) {&lt;br /&gt;
        var x = normalizeNumber(entry &amp;amp;&amp;amp; entry.x, 0);&lt;br /&gt;
        var y = normalizeNumber(entry &amp;amp;&amp;amp; entry.y, 0);&lt;br /&gt;
        var sourceX = x;&lt;br /&gt;
        var sourceY = y;&lt;br /&gt;
        var sourceSelector;&lt;br /&gt;
        var sourceTarget;&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var actualRect;&lt;br /&gt;
        var sourceOriginX;&lt;br /&gt;
        var sourceOriginY;&lt;br /&gt;
        var actualOriginX;&lt;br /&gt;
        var actualOriginY;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문 데코 좌표 보존&lt;br /&gt;
        -----------------------------------------&lt;br /&gt;
        저장된 x/y는 기존 entry.target 좌표계를 기준으로 만들어졌다.&lt;br /&gt;
        데코를 main-body-well 안으로 옮길 때 x/y를 그대로 적용하면&lt;br /&gt;
        더 아래에서 시작하는 새 좌표계 때문에 장식이 바닥으로 밀린다.&lt;br /&gt;
&lt;br /&gt;
        실제 화면상의 위치는 유지하고 부모만 우물로 바꾼다.&lt;br /&gt;
        기존 기준면의 padding-box 원점과 새 우물의 padding-box 원점 차이를&lt;br /&gt;
        정수 px로 더해 새 로컬 좌표를 만든다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            currentPageKey() === &#039;대문&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList.contains(&#039;main-body-well&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
            !usesCanonicalMainPageCoordinates(entry, actualTarget)&lt;br /&gt;
        ) {&lt;br /&gt;
            sourceSelector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() ||&lt;br /&gt;
                &#039;.liberty-content-main&#039;;&lt;br /&gt;
            sourceTarget = queryDecorationTarget(sourceSelector) ||&lt;br /&gt;
                document.querySelector(&#039;.liberty-content-main&#039;) ||&lt;br /&gt;
                document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (sourceTarget &amp;amp;&amp;amp; sourceTarget !== actualTarget) {&lt;br /&gt;
                sourceRect = sourceTarget.getBoundingClientRect();&lt;br /&gt;
                actualRect = actualTarget.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
                sourceOriginX = sourceRect.left + normalizeNumber(sourceTarget.clientLeft, 0);&lt;br /&gt;
                sourceOriginY = sourceRect.top + normalizeNumber(sourceTarget.clientTop, 0);&lt;br /&gt;
                actualOriginX = actualRect.left + normalizeNumber(actualTarget.clientLeft, 0);&lt;br /&gt;
                actualOriginY = actualRect.top + normalizeNumber(actualTarget.clientTop, 0);&lt;br /&gt;
&lt;br /&gt;
                x += Math.round(sourceOriginX - actualOriginX);&lt;br /&gt;
                y += Math.round(sourceOriginY - actualOriginY);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            x: Math.round(x),&lt;br /&gt;
            y: Math.round(y),&lt;br /&gt;
            sourceX: Math.round(sourceX),&lt;br /&gt;
            sourceY: Math.round(sourceY),&lt;br /&gt;
            sourceSelector: sourceSelector || &#039;&#039;&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecorationBaseStyle(node, entry, actualTarget) {&lt;br /&gt;
        var position = getDecorationLocalPosition(entry, actualTarget);&lt;br /&gt;
&lt;br /&gt;
        node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
        node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
        node.style.opacity = String(normalizeNumber(entry.opacity, 1));&lt;br /&gt;
        node.style.zIndex = String(Math.round(normalizeNumber(entry.zIndex, 0)));&lt;br /&gt;
        node.style.pointerEvents = String(entry.pointerEvents || &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (position.sourceSelector) {&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-coordinate-source&#039;, position.sourceSelector);&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-x&#039;, String(position.sourceX));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-y&#039;, String(position.sourceY));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (entry.blendMode) node.style.mixBlendMode = String(entry.blendMode);&lt;br /&gt;
        if (entry.filter) node.style.filter = String(entry.filter);&lt;br /&gt;
        if (entry.transform) node.style.transform = String(entry.transform);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDecorationNodeActiveForNationsState(node) {&lt;br /&gt;
        var era = node ? String(node.getAttribute(&#039;data-decoration-era&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        var continent = node ? String(node.getAttribute(&#039;data-decoration-continent&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; era !== getActiveNationsEra()) return false;&lt;br /&gt;
        if (continent &amp;amp;&amp;amp; continent !== getActiveNationsContinent()) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDecorationNodeVisibility(node, visible) {&lt;br /&gt;
        if (!node) return;&lt;br /&gt;
        visible = visible !== false;&lt;br /&gt;
        /* aria-hidden stays true because wiki decorations are purely visual. */&lt;br /&gt;
        if (node.hidden !== !visible) node.hidden = !visible;&lt;br /&gt;
        if (node.style.display !== (visible ? &#039;&#039; : &#039;none&#039;)) node.style.display = visible ? &#039;&#039; : &#039;none&#039;;&lt;br /&gt;
        node.setAttribute(&#039;data-decoration-visible&#039;, visible ? &#039;1&#039; : &#039;0&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateDecorationVisibility(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var count = 0;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            setDecorationNodeVisibility(node, isDecorationNodeActiveForNationsState(node));&lt;br /&gt;
            count += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return count;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasRenderedDecorations(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return !!(scope &amp;amp;&amp;amp; scope.querySelector &amp;amp;&amp;amp; scope.querySelector(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPixelJsonDecoration(entry, target, visible) {&lt;br /&gt;
        var ref = getPixelJsonRef(entry);&lt;br /&gt;
        var template;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var ctx;&lt;br /&gt;
        if (!ref) return false;&lt;br /&gt;
&lt;br /&gt;
        function makeCanvasFromTemplate(source) {&lt;br /&gt;
            var out = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            var outCtx;&lt;br /&gt;
            out.className = &#039;wiki-decoration wiki-decoration-pixel-json&#039;;&lt;br /&gt;
            out.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
            out.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-type&#039;, &#039;pixel-json&#039;);&lt;br /&gt;
            if (entry.placement) out.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
            if (entry.era) out.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
            if (entry.continent) out.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
            out.width = source.width;&lt;br /&gt;
            out.height = source.height;&lt;br /&gt;
            out.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            out.style.width = source.width + &#039;px&#039;;&lt;br /&gt;
            out.style.height = source.height + &#039;px&#039;;&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-size&#039;, source.width + &#039;x&#039; + source.height);&lt;br /&gt;
            outCtx = out.getContext(&#039;2d&#039;);&lt;br /&gt;
            outCtx.imageSmoothingEnabled = false;&lt;br /&gt;
            outCtx.drawImage(source, 0, 0);&lt;br /&gt;
            applyDecorationBaseStyle(out, entry, target);&lt;br /&gt;
            setDecorationNodeVisibility(out, visible);&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template = getPreparedPixelCanvasSync(ref);&lt;br /&gt;
        if (template) {&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(template));&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Fallback path only.  A full entry pack should prepare the template before the&lt;br /&gt;
           normal UI is released, so users should not see a blank decoration canvas. */&lt;br /&gt;
        preparePixelCanvas(ref).then(function (source) {&lt;br /&gt;
            if (!target || !target.parentNode || !source) return;&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(source));&lt;br /&gt;
        }).catch(function () {});&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preloadPixelAssetsForRegistry(registry) {&lt;br /&gt;
        var promises = [];&lt;br /&gt;
        decorationList(registry).forEach(function (entry) {&lt;br /&gt;
            var ref;&lt;br /&gt;
            if (!entry || !matchesPage(entry) || !isPixelJsonDecoration(entry)) return;&lt;br /&gt;
            ref = getPixelJsonRef(entry);&lt;br /&gt;
            if (!ref) return;&lt;br /&gt;
            promises.push(preparePixelCanvas(ref).catch(function () { return null; }));&lt;br /&gt;
        });&lt;br /&gt;
        return Promise.all(promises);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decorationList(registry) {&lt;br /&gt;
        if (!registry || typeof registry !== &#039;object&#039;) return [];&lt;br /&gt;
        if (Array.isArray(registry)) return registry;&lt;br /&gt;
        if (Array.isArray(registry.decorations)) return registry.decorations;&lt;br /&gt;
        return [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesPage(entry) {&lt;br /&gt;
        var page = currentPageKey();&lt;br /&gt;
        var underscored = page.replace(/ /g, &#039;_&#039;);&lt;br /&gt;
        var pages = entry &amp;amp;&amp;amp; entry.pages;&lt;br /&gt;
        var target = entry &amp;amp;&amp;amp; entry.page;&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        if (normalizeBool(entry &amp;amp;&amp;amp; entry.global, false)) return true;&lt;br /&gt;
        if (String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;boot-gate&#039; || String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;loading-screen&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (normalizePageName(target).toLowerCase() === &#039;__boot__&#039; || normalizePageName(target).toLowerCase() === &#039;loading-screen&#039;) return true;&lt;br /&gt;
        if (!target &amp;amp;&amp;amp; !pages) return true;&lt;br /&gt;
&lt;br /&gt;
        if (Array.isArray(pages)) {&lt;br /&gt;
            for (i = 0; i &amp;lt; pages.length; i += 1) {&lt;br /&gt;
                if (normalizePageName(pages[i]) === page || String(pages[i] || &#039;&#039;) === underscored) return true;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        target = normalizePageName(target);&lt;br /&gt;
        return target === page || target === underscored;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearRendered(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureHost(target) {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (!target) return;&lt;br /&gt;
        target.setAttribute(HOST_ATTR, &#039;1&#039;);&lt;br /&gt;
        style = window.getComputedStyle ? window.getComputedStyle(target) : null;&lt;br /&gt;
        if (style &amp;amp;&amp;amp; style.position === &#039;static&#039;) target.style.position = &#039;relative&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecoration(entry) {&lt;br /&gt;
        var resolved;&lt;br /&gt;
        var target;&lt;br /&gt;
        var src;&lt;br /&gt;
        var img;&lt;br /&gt;
        var width;&lt;br /&gt;
        var height;&lt;br /&gt;
&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039; || normalizeBool(entry.enabled, true) === false) return false;&lt;br /&gt;
        resolved = resolveDecorationPlacement(entry);&lt;br /&gt;
        if (!resolved) return false;&lt;br /&gt;
        target = resolved.target;&lt;br /&gt;
        if (!target) return false;&lt;br /&gt;
&lt;br /&gt;
        ensureHost(target);&lt;br /&gt;
&lt;br /&gt;
        if (isPixelJsonDecoration(entry)) {&lt;br /&gt;
            return applyPixelJsonDecoration(entry, target, resolved.visible !== false);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        src = normalizeSrc(entry.src);&lt;br /&gt;
        if (!src) return false;&lt;br /&gt;
&lt;br /&gt;
        img = document.createElement(&#039;img&#039;);&lt;br /&gt;
        img.className = &#039;wiki-decoration&#039;;&lt;br /&gt;
        img.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
        img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, &#039;&#039;);&lt;br /&gt;
        img.setAttribute(&#039;decoding&#039;, &#039;async&#039;);&lt;br /&gt;
        img.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        img.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
        if (entry.placement) img.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
        if (entry.era) img.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
        if (entry.continent) img.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
        img.src = src;&lt;br /&gt;
&lt;br /&gt;
        applyDecorationBaseStyle(img, entry, target);&lt;br /&gt;
        width = normalizeNumber(entry.width, NaN);&lt;br /&gt;
        height = normalizeNumber(entry.height, NaN);&lt;br /&gt;
        if (Number.isFinite(width) &amp;amp;&amp;amp; width &amp;gt; 0) img.style.width = width + &#039;px&#039;;&lt;br /&gt;
        if (Number.isFinite(height) &amp;amp;&amp;amp; height &amp;gt; 0) img.style.height = height + &#039;px&#039;;&lt;br /&gt;
        if (entry.objectFit) img.style.objectFit = String(entry.objectFit);&lt;br /&gt;
        setDecorationNodeVisibility(img, resolved.visible !== false);&lt;br /&gt;
&lt;br /&gt;
        target.appendChild(img);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mainPageLayoutSignature() {&lt;br /&gt;
        var source = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
        var well = ensureMainPageBodyWell();&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var wellRect;&lt;br /&gt;
&lt;br /&gt;
        if (!source || !well) return &#039;&#039;;&lt;br /&gt;
        sourceRect = source.getBoundingClientRect();&lt;br /&gt;
        wellRect = well.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            Math.round((wellRect.left + normalizeNumber(well.clientLeft, 0)) - (sourceRect.left + normalizeNumber(source.clientLeft, 0))),&lt;br /&gt;
            Math.round((wellRect.top + normalizeNumber(well.clientTop, 0)) - (sourceRect.top + normalizeNumber(source.clientTop, 0))),&lt;br /&gt;
            Math.round(wellRect.width),&lt;br /&gt;
            Math.round(wellRect.height)&lt;br /&gt;
        ].join(&#039;|&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMainPageDecorationLayoutReady() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var panel = portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var topMount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!portal || !panel || !ensureMainPageBodyWell()) return false;&lt;br /&gt;
        if (!document.body || !document.body.classList.contains(&#039;clbi-main-page&#039;)) return false;&lt;br /&gt;
        if (topMount &amp;amp;&amp;amp; !topMount.querySelector(&#039;.portal-category-nav&#039;)) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForMainPageDecorationLayout() {&lt;br /&gt;
        var started;&lt;br /&gt;
        var previous = &#039;&#039;;&lt;br /&gt;
        var stableFrames = 0;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return Promise.resolve(true);&lt;br /&gt;
        started = Date.now();&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function check() {&lt;br /&gt;
                var signature = mainPageLayoutSignature();&lt;br /&gt;
&lt;br /&gt;
                if (signature &amp;amp;&amp;amp; signature === previous) stableFrames += 1;&lt;br /&gt;
                else stableFrames = 0;&lt;br /&gt;
                previous = signature;&lt;br /&gt;
&lt;br /&gt;
                if ((isMainPageDecorationLayoutReady() &amp;amp;&amp;amp; stableFrames &amp;gt;= 2) || Date.now() - started &amp;gt;= 1200) {&lt;br /&gt;
                    resolve(true);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (window.requestAnimationFrame) window.requestAnimationFrame(check);&lt;br /&gt;
                else window.setTimeout(check, 16);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebaseLegacyMainPageDecorations() {&lt;br /&gt;
        var well;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return;&lt;br /&gt;
        well = ensureMainPageBodyWell();&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-coordinate-source]&#039;).forEach(function (node) {&lt;br /&gt;
            var position = getDecorationLocalPosition({&lt;br /&gt;
                target: node.getAttribute(&#039;data-decoration-coordinate-source&#039;) || &#039;.liberty-content-main&#039;,&lt;br /&gt;
                x: normalizeNumber(node.getAttribute(&#039;data-decoration-source-x&#039;), 0),&lt;br /&gt;
                y: normalizeNumber(node.getAttribute(&#039;data-decoration-source-y&#039;), 0)&lt;br /&gt;
            }, well);&lt;br /&gt;
&lt;br /&gt;
            node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
            node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleMainPageDecorationRebase() {&lt;br /&gt;
        if (scheduledMainPageRebase) return;&lt;br /&gt;
        scheduledMainPageRebase = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindMainPagePlacementRefresh() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var nodes;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039; || !portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = null;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (observedMainPagePortal !== portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = portal;&lt;br /&gt;
&lt;br /&gt;
            if (typeof ResizeObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementObserver = new ResizeObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                nodes = [&lt;br /&gt;
                    document.querySelector(&#039;.liberty-content-main&#039;),&lt;br /&gt;
                    portal,&lt;br /&gt;
                    portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-panel&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-well&#039;)&lt;br /&gt;
                ];&lt;br /&gt;
                nodes.forEach(function (node) {&lt;br /&gt;
                    if (node) mainPagePlacementObserver.observe(node);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementMutationObserver = new MutationObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                mainPagePlacementMutationObserver.observe(portal, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        scheduleMainPageDecorationRebase();&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 80);&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderForCurrentLayout(registry, token) {&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return Promise.resolve(registry);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return waitForMainPageDecorationLayout().then(function () {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return registry;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(registry) {&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        lastRegistry = registry || { decorations: [] };&lt;br /&gt;
        lastRenderedPageKey = currentPageKey();&lt;br /&gt;
        bindNationsPlacementRefresh();&lt;br /&gt;
        clearRendered(document);&lt;br /&gt;
        decorationList(lastRegistry).forEach(function (entry) {&lt;br /&gt;
            if (token === runtimeToken &amp;amp;&amp;amp; matchesPage(entry)) applyDecoration(entry);&lt;br /&gt;
        });&lt;br /&gt;
        bindMainPagePlacementRefresh();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPrepared() {&lt;br /&gt;
        var registry = getPreparedRegistrySync() || lastRegistry;&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        if (!registry) return false;&lt;br /&gt;
        renderForCurrentLayout(registry, token);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function syncDecorationState() {&lt;br /&gt;
        if (lastRenderedPageKey === currentPageKey() &amp;amp;&amp;amp; hasRenderedDecorations(document)) {&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (lastRegistry) {&lt;br /&gt;
            renderForCurrentLayout(lastRegistry, runtimeToken);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        return renderPrepared();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleRenderFromCache() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleDecorationVisibilityUpdate() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindNationsPlacementRefresh() {&lt;br /&gt;
        var stack = document.querySelector(&#039;.clbi-nations-panel-stack&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!stack || observedNationsStack === stack) return;&lt;br /&gt;
        observedNationsStack = stack;&lt;br /&gt;
&lt;br /&gt;
        if (nationsPlacementObserver) {&lt;br /&gt;
            nationsPlacementObserver.disconnect();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
            nationsPlacementObserver = new MutationObserver(scheduleDecorationVisibilityUpdate);&lt;br /&gt;
            nationsPlacementObserver.observe(stack, {&lt;br /&gt;
                subtree: true,&lt;br /&gt;
                attributes: true,&lt;br /&gt;
                attributeFilter: [&#039;class&#039;, &#039;hidden&#039;, &#039;aria-selected&#039;, &#039;data-current-era&#039;, &#039;data-nations-current-era&#039;, &#039;data-era-year&#039;]&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedRegistrySync() {&lt;br /&gt;
        var registry = null;&lt;br /&gt;
        var url;&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.getJsonSync === &#039;function&#039;) {&lt;br /&gt;
                registry = window.EntryStore.getJsonSync(REGISTRY_TITLE);&lt;br /&gt;
                if (!registry &amp;amp;&amp;amp; mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                    url = mw.util.getUrl(REGISTRY_TITLE, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
                    registry = window.EntryStore.getJsonSync(url);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return registry &amp;amp;&amp;amp; typeof registry === &#039;object&#039; ? registry : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchRegistry() {&lt;br /&gt;
        var url;&lt;br /&gt;
        var prepared = getPreparedRegistrySync();&lt;br /&gt;
        if (prepared) return Promise.resolve(prepared);&lt;br /&gt;
        if (!mw || !mw.util || typeof fetch !== &#039;function&#039;) return Promise.resolve({ decorations: [] });&lt;br /&gt;
        url = mw.util.getUrl(REGISTRY_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
            .then(function (response) {&lt;br /&gt;
                if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                return response.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (!text || !text.trim()) return { decorations: [] };&lt;br /&gt;
                return JSON.parse(text);&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                return { decorations: [] };&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function reload() {&lt;br /&gt;
        var token;&lt;br /&gt;
        runtimeToken += 1;&lt;br /&gt;
        token = runtimeToken;&lt;br /&gt;
        return fetchRegistry().then(function (registry) {&lt;br /&gt;
            return preloadPixelAssetsForRegistry(registry).then(function () {&lt;br /&gt;
                return renderForCurrentLayout(registry, token);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
        var target = event.target &amp;amp;&amp;amp; event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent], [data-nations-era-move]&#039;) : null;&lt;br /&gt;
        if (target) window.setTimeout(scheduleDecorationVisibilityUpdate, 0);&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    function decorationDiagnostics() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: &#039;20260713-main-body-well-coordinate-003&#039;,&lt;br /&gt;
            hasRegistry: !!lastRegistry,&lt;br /&gt;
            entries: decorationList(lastRegistry).length,&lt;br /&gt;
            rendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            preparedRegistry: !!getPreparedRegistrySync(),&lt;br /&gt;
            pixelAssets: Object.keys(pixelAssetCache || {}).length,&lt;br /&gt;
            pixelCanvases: Object.keys(pixelCanvasCache || {}).length,&lt;br /&gt;
            visibilityOnlySync: true,&lt;br /&gt;
            mainPageCoordinateSurface: MAIN_PAGE_TARGET,&lt;br /&gt;
            mainPagePlacementObserved: !!observedMainPagePortal,&lt;br /&gt;
            lastRenderedPage: lastRenderedPageKey,&lt;br /&gt;
            visibleRendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-visible=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            scheduled: !!scheduledRender&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.Decorations = window.Decorations || {};&lt;br /&gt;
    window.Decorations.reload = reload;&lt;br /&gt;
    window.Decorations.render = render;&lt;br /&gt;
    window.Decorations.renderPrepared = renderPrepared;&lt;br /&gt;
    window.Decorations.sync = syncDecorationState;&lt;br /&gt;
    window.Decorations.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.Decorations.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.Decorations.clear = clearRendered;&lt;br /&gt;
    window.Decorations.apply = applyDecoration;&lt;br /&gt;
    window.Decorations.pageKey = currentPageKey;&lt;br /&gt;
    window.Decorations.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.Decorations.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.Decorations.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.Decorations.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_DECORATIONS = window.CLBI_DECORATIONS || {};&lt;br /&gt;
    window.CLBI_DECORATIONS.reload = reload;&lt;br /&gt;
    window.CLBI_DECORATIONS.render = render;&lt;br /&gt;
    window.CLBI_DECORATIONS.renderPrepared = renderPrepared;&lt;br /&gt;
    window.CLBI_DECORATIONS.sync = syncDecorationState;&lt;br /&gt;
    window.CLBI_DECORATIONS.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.CLBI_DECORATIONS.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.CLBI_DECORATIONS.clear = clearRendered;&lt;br /&gt;
    window.CLBI_DECORATIONS.apply = applyDecoration;&lt;br /&gt;
    window.CLBI_DECORATIONS.pageKey = currentPageKey;&lt;br /&gt;
    window.CLBI_DECORATIONS.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.CLBI_DECORATIONS.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.CLBI_DECORATIONS.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.CLBI_DECORATIONS.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, reload);&lt;br /&gt;
    } else {&lt;br /&gt;
        reload();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            reload();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Unified Shortcuts loader&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!window.mw || !mw.loader) return;&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Nation_List_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:History_Event_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Shortcuts.js&#039;);&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Main page manifesto bitmap renderer&lt;br /&gt;
   Integrated build: no extra raw-script page, no image/data-URL decoding.&lt;br /&gt;
   New shared systems do not use the legacy CLBI prefix.&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Retired compatibility block&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    선언문은 실제 HTML과 ManifestoIntro의 실선 요소만 사용한다.&lt;br /&gt;
    이 구식 렌더러는 제목·본문과 완성형 구분선을 canvas에 한 번에 그려&lt;br /&gt;
    새 애니메이션의 선 뒤에 트랙이 나타날 수 있었다.&lt;br /&gt;
&lt;br /&gt;
    기존 Common.js를 장기 캐시한 뒤 새 파일로 넘어오는 경우를 위해&lt;br /&gt;
    남아 있는 canvas와 상태만 제거하고 즉시 종료한다. 새 공개 이름에는&lt;br /&gt;
    프로젝트 접두사를 사용하지 않는 규칙을 유지한다.&lt;br /&gt;
    */&lt;br /&gt;
    function removeRetiredBitmap() {&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-bitmap&#039;),&lt;br /&gt;
            function (canvas) {&lt;br /&gt;
                if (canvas.parentNode) canvas.parentNode.removeChild(canvas);&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-inner&#039;),&lt;br /&gt;
            function (inner) {&lt;br /&gt;
                inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;data-bitmap-width&#039;,&lt;br /&gt;
                    &#039;data-bitmap-optical-shift-x&#039;,&lt;br /&gt;
                    &#039;data-bitmap-state&#039;,&lt;br /&gt;
                    &#039;data-bitmap-error&#039;&lt;br /&gt;
                ].forEach(function (name) {&lt;br /&gt;
                    inner.removeAttribute(name);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    removeRetiredBitmap();&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(removeRetiredBitmap);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreRetiredBitmapHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:&#039;retired-20260713-divider-no-track-001&#039;,&lt;br /&gt;
        recalculate:removeRetiredBitmap,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            return {&lt;br /&gt;
                integrated:false,&lt;br /&gt;
                state:&#039;retired&#039;,&lt;br /&gt;
                canvas:!!document.querySelector(&#039;.main-portal .main-manifesto-bitmap&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    var VERSION = &#039;20260712-bitmap18-inline-002&#039;;&lt;br /&gt;
    var SELECTOR = &#039;.main-portal .main-manifesto-inner&#039;;&lt;br /&gt;
    var CELL_W = 24;&lt;br /&gt;
    var CELL_H = 24;&lt;br /&gt;
    var GLYPHS = {&amp;quot;진&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,25056,25072,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;창&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,13280,78328,258272,12768,14128,13848,4360,3840,7040,12672,4480,8064,3840,0,0],&amp;quot;바&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12672,12672,14080,13056,13064,258456,127472,12784,12504,12408,12312,12288,12288,4096,0,0],&amp;quot;다&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,12792,12400,12320,258096,128528,14328,13048,12304,12288,12288,12288,4096,0,0],&amp;quot;는&amp;quot;:[19,0,0,0,0,0,0,96,192,192,14528,16064,960,0,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;왕&amp;quot;:[19,0,0,0,0,0,12288,28768,25568,25456,25392,25392,254944,24960,16064,10236,12344,7680,14080,12672,12672,16128,7936,0,0],&amp;quot;과&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,26560,26352,26112,26112,26208,255680,254912,29248,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;노&amp;quot;:[19,0,0,0,0,0,0,0,0,96,192,192,4288,15552,16320,7040,7168,3072,50688,131064,124,0,0,0,0],&amp;quot;예&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,24048,24504,23320,23320,24504,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;를&amp;quot;:[19,0,0,0,0,0,7168,16320,6144,8128,448,15552,1984,122880,262140,16636,8128,8128,3840,8064,1792,960,65408,4096,0],&amp;quot;같&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,110976,127168,12384,12344,4104,3584,0,8064,1920,256,14720,8064,0,0],&amp;quot;은&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,130944,115708,16,448,448,128,128,16256,7936,0,0],&amp;quot;깊&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,25024,24800,24624,8216,12288,16256,4864,15872,7680,31232,32640,256,0],&amp;quot;이&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,25072,25584,25368,25368,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;로&amp;quot;:[19,0,0,0,0,0,0,0,15360,16320,6272,7168,8128,384,14528,16320,7040,3072,50688,131064,124,0,0,0,0],&amp;quot;삼&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,78064,258272,13296,14136,13324,4096,16256,16256,4224,4480,8064,8064,0,0],&amp;quot;켰&amp;quot;:[19,0,0,0,0,0,12288,24576,26112,26600,29560,32656,25072,31992,26720,8224,8216,13056,15104,5056,15232,28544,58992,0,0],&amp;quot;고&amp;quot;:[19,0,0,0,0,0,0,0,0,7680,16352,12416,12288,12288,12544,13056,6912,4864,49920,131064,124,0,0,0,0],&amp;quot;,&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,8,0,0,0],&amp;quot;겨&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,32560,31104,24768,31840,31792,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;울&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,114688,262140,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;사&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12352,12480,12480,12480,12400,258272,127920,14104,13828,13312,12288,12288,12288,4096,0,0],&amp;quot;람&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13296,13240,78208,258552,12304,14224,13296,4096,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;짐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,26592,26160,9756,0,32512,32640,12672,12672,16128,16128,0,0],&amp;quot;승&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,130816,115708,272,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;의&amp;quot;:[19,0,0,0,0,0,28672,57344,24608,25536,26592,26160,26160,26464,25568,24576,24576,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;살&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,110832,127456,13232,13848,5132,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;을&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,32768,130944,115708,0,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;빛&amp;quot;:[19,0,0,0,0,0,12288,24576,25344,25344,26376,25496,25592,25584,25552,25088,7168,12288,32640,6400,7680,32256,25472,0,0],&amp;quot;으&amp;quot;:[19,0,0,0,0,0,0,0,128,3968,8064,4544,4288,6336,7616,3968,0,0,57344,131068,56,0,0,0,0],&amp;quot;얼&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25008,32536,31512,25016,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;렸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,32568,27008,31224,31792,26544,9200,8192,13056,15104,4928,15232,28544,58976,0,0],&amp;quot;.&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0],&amp;quot;그&amp;quot;:[19,0,0,0,0,0,0,0,0,7168,16352,12672,12288,12288,12288,12288,6144,4096,57344,131068,56,0,0,0,0],&amp;quot;들&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130048,118780,120,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;돌&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,8128,130048,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;벽&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25344,32520,31512,25560,32632,25584,9104,8192,31744,32640,8192,8192,8192,8192,0,0],&amp;quot;세&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22720,22720,22752,24432,22752,22960,23320,23300,22528,18432,16384,16384,24576,0,0],&amp;quot;우&amp;quot;:[19,0,0,0,0,0,0,384,7936,6528,12480,6336,7552,3968,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;땅&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,28560,9200,74160,254128,30936,16376,12280,12544,7936,13184,12672,12672,8064,3840,0,0],&amp;quot;찢&amp;quot;:[19,0,0,0,0,0,12288,24576,24704,26616,28656,26560,25456,28656,28056,26848,8192,32512,16128,7680,15872,25344,16768,0,0],&amp;quot;어&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,25520,32536,27416,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;길&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,24768,24672,24624,8216,16256,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;냈&amp;quot;:[19,0,0,0,0,0,8192,58368,19456,27672,27704,31792,31792,32560,28656,27120,8704,13824,14080,13248,15232,28544,50272,0,0],&amp;quot;며&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,26600,32536,25368,25400,32560,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;쇠&amp;quot;:[19,0,0,0,0,0,28672,57344,24704,24960,24960,25056,25536,26208,27888,24960,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;에&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,23024,23480,24344,24344,22968,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;제&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23488,23032,22976,24512,24184,23024,23344,23320,23052,18432,16384,16384,24576,0,0],&amp;quot;름&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,384,130560,116732,56,15872,16320,4288,4544,7552,8064,0,0],&amp;quot;새&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,27840,27840,31968,31856,23776,23984,20248,20228,16384,16384,16384,16384,24576,0,0],&amp;quot;겼&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,30712,32640,25024,31968,26672,8216,8200,15104,15104,6976,15232,28608,58992,0,0],&amp;quot;감&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,4120,4096,16256,16320,4224,4480,8064,8064,0,0],&amp;quot;옥&amp;quot;:[19,0,0,0,0,0,1792,7936,6528,6272,6528,8064,3584,1536,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;되&amp;quot;:[19,0,0,0,0,0,28672,57344,25088,26608,25072,24672,25632,26416,26608,25456,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;군&amp;quot;:[19,0,0,0,0,0,0,7680,16352,12288,12288,12288,6144,126976,262140,18172,1536,1728,1472,192,192,16256,8064,0,0],&amp;quot;대&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28032,28144,31856,31776,23600,24344,20440,19960,16400,16384,16384,16384,24576,0,0],&amp;quot;나&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12296,12312,12336,12304,259600,128784,14256,12528,12320,12288,12288,12288,4096,0,0],&amp;quot;르&amp;quot;:[19,0,0,0,0,0,0,0,6144,16320,6336,7168,8128,448,12480,16320,3968,0,57344,131068,56,0,0,0,0],&amp;quot;오&amp;quot;:[19,0,0,0,0,0,0,0,256,7936,15232,12672,12416,12672,8064,7936,3072,1024,49664,131064,124,0,0,0,0],&amp;quot;래&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28144,27064,32128,32248,23664,23568,20240,20464,16880,16384,16384,16384,24576,0,0],&amp;quot;된&amp;quot;:[19,0,0,0,0,0,28672,57344,26368,25584,24800,25696,28656,26608,29440,32736,25080,24832,25344,768,768,65280,15872,0,0],&amp;quot;원&amp;quot;:[19,0,0,0,0,0,28672,57440,25536,26464,26160,26160,29664,32640,26592,26620,25360,25024,25536,768,768,65280,15872,0,0],&amp;quot;한&amp;quot;:[19,0,0,0,0,0,6144,28704,12512,12800,14328,12784,258528,13088,12592,12784,12512,12416,4544,384,128,16256,7936,0,0],&amp;quot;함&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,14328,78032,258528,13104,12720,12784,4192,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;께&amp;quot;:[19,0,0,0,0,0,24576,55296,55296,55296,55488,57336,57280,57184,57184,55600,55704,55500,55392,49152,49152,16384,16384,0,0],&amp;quot;음&amp;quot;:[19,0,0,0,0,0,1920,8064,6528,6336,6336,3968,1792,32768,131040,16892,0,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;손&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,26304,1584,130560,249852,120,448,448,128,128,16256,7936,0,0],&amp;quot;넘&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,31792,31792,24624,28208,26608,8416,0,32512,32640,12672,12672,16256,15616,0,0],&amp;quot;갔&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,12312,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;\n&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],&amp;quot;줄&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,3840,15296,12480,41056,130976,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;처&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,25072,32448,27616,25392,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;부&amp;quot;:[19,0,0,0,0,0,6144,12288,28768,14400,15552,13248,8128,7872,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;터&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25056,24768,25344,25584,31984,31840,25648,28568,26360,24624,24576,24576,8192,8192,0,0],&amp;quot;목&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;둘&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130944,116732,1592,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;레&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23024,22968,22912,24568,22640,22544,23312,23536,23024,18432,16384,16384,24576,0,0],&amp;quot;있&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,25400,25368,25368,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;었&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32568,32536,25496,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;끝&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,3840,3584,8128,1920,384,16320,4032,0,0],&amp;quot;속&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,7936,12672,26336,1568,130560,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;뿌&amp;quot;:[19,0,0,0,0,0,4096,30720,14080,15904,16352,13280,16320,14208,4608,130944,126972,1552,1536,1536,1536,1536,512,0,0],&amp;quot;리&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,24960,25040,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;와&amp;quot;:[19,0,0,0,0,0,12288,28672,24608,25568,25456,26160,26416,25584,254432,254336,28864,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;빨&amp;quot;:[19,0,0,0,0,0,12288,28672,26112,26176,26304,255944,255992,10200,10224,1728,16256,16256,7936,7936,1792,896,65024,0,0],&amp;quot;굶&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,118784,262140,18428,1536,14080,16368,13792,14272,14272,16352,16352,0,0],&amp;quot;주&amp;quot;:[19,0,0,0,0,0,0,14336,16320,3264,1792,7936,12736,24672,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;림&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,25400,25472,25072,24624,26544,9200,8192,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;매&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28544,28644,32156,32024,23832,23992,19952,19504,16384,16384,16384,16384,24576,0,0],&amp;quot;였&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,32752,32568,25368,32664,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;른&amp;quot;:[19,0,0,0,0,0,4096,16320,6336,7744,4032,14528,16320,896,126976,253948,120,64,448,128,128,16256,7936,0,0],&amp;quot;아&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12304,12528,12720,13080,258840,127896,12784,12352,12288,12288,12288,12288,4096,0,0],&amp;quot;직&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,25568,26160,9752,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;태&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27840,28032,32240,31856,23600,24336,20440,20472,16432,16384,16384,16384,24576,0,0],&amp;quot;지&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25584,24992,24768,24816,25568,26160,27672,25612,24576,24576,24576,8192,0,0],&amp;quot;않&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,7168,6240,32352,15456,13408,14176,8160,0,0],&amp;quot;자&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13304,12720,12480,258296,127984,14128,13848,13324,12288,12288,12288,4096,0,0],&amp;quot;졌&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,32752,31136,32240,32736,26160,9756,8704,13056,15104,4928,15232,28544,50784,0,0],&amp;quot;앞&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,6144,8128,2432,7936,2816,15616,16320,128,0],&amp;quot;갈&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,127360,127168,12384,12344,4104,16256,8064,3840,8064,1792,896,65280,0,0],&amp;quot;때&amp;quot;:[19,0,0,0,0,0,24576,52224,55296,55296,57096,56312,63920,63632,63704,56536,57336,57336,49216,49152,49152,16384,16384,0,0],&amp;quot;마&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13284,13084,13080,258840,127792,12784,12464,12288,12288,12288,12288,4096,0,0],&amp;quot;듭&amp;quot;:[19,0,0,0,0,0,6144,16320,1984,384,12480,16064,3008,32768,131008,115708,14336,12352,12480,14720,16256,8064,7808,0,0],&amp;quot;파&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13248,12792,12688,13296,258528,127392,16352,12412,12288,12288,12288,12288,4096,0,0],&amp;quot;각&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,12672,258240,12384,12336,4120,4096,16128,13248,12288,4096,4096,4096,0,0],&amp;quot;쥔&amp;quot;:[19,0,0,0,0,0,28672,57856,26608,25584,25024,26592,28208,27664,28544,28668,25360,24960,25536,768,768,65280,15872,0,0],&amp;quot;당&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,12536,77920,258096,15888,14328,12920,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;기&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,25392,24960,24768,24672,24624,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;면&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26604,32536,25368,32560,31728,25072,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;풀&amp;quot;:[19,0,0,0,0,0,12288,16352,7296,8064,3328,32704,41440,131008,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;려&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,32696,31104,25040,31856,27696,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;날&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12312,12344,110640,128560,14256,13296,4192,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;것&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,24960,32704,26848,24624,24600,11272,3072,1792,3584,15104,29056,24800,0,0],&amp;quot;라&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13280,12728,12672,12792,258168,127504,13840,14320,12784,12288,12288,12288,4096,0,0],&amp;quot;여&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,32688,25368,25368,32664,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;므&amp;quot;:[19,0,0,0,0,0,0,0,4096,31744,32736,6624,3264,1728,16064,7360,0,0,57344,131068,56,0,0,0,0],&amp;quot;번&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25344,26376,26392,32728,25464,25584,25552,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;도&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,4064,960,384,14528,16064,7136,7360,3072,50688,131064,124,0,0,0,0],&amp;quot;느&amp;quot;:[19,0,0,0,0,0,0,0,0,32,192,192,192,14528,16064,7104,0,0,57344,131068,56,0,0,0,0],&amp;quot;슨&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,24768,48,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;해&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27968,28664,32240,32224,23984,23984,19952,19568,16384,16384,16384,16384,24576,0,0],&amp;quot;았&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12528,12784,78616,258840,13208,12784,12512,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;침&amp;quot;:[19,0,0,0,0,0,12288,24768,25024,25088,26592,25584,24800,25568,26416,9232,8,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;내&amp;quot;:[19,0,0,0,0,0,24576,58368,19456,19456,27656,27672,31792,31792,31760,24368,20464,19952,16384,16384,24576,24576,8192,0,0],&amp;quot;멎&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25576,32536,31512,25392,25072,8224,8192,32640,6912,3584,15872,25344,16768,0,0],&amp;quot;서&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,32496,24800,25520,26392,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;긴&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26368,25592,25520,25024,24768,24688,24632,24584,25472,8960,768,768,32512,15872,0,0],&amp;quot;붙&amp;quot;:[19,0,0,0,0,0,6144,12288,14432,16064,13248,7872,38912,130816,116732,1552,3840,3584,8064,1920,384,15808,4032,0,0],&amp;quot;잡&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,111040,258288,13280,13872,5660,4096,12352,12416,14720,16256,8064,7296,0,0],&amp;quot;피&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25592,25392,25568,25440,24992,28640,26744,24576,24576,24576,24576,8192,0,0],&amp;quot;가&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,13304,12728,12736,127168,258144,12336,12312,12300,12288,12288,12288,4096,0,0],&amp;quot;굴&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,126976,262140,18172,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;보&amp;quot;:[19,0,0,0,0,0,0,0,6144,12288,28768,14528,16064,13248,16320,7360,3072,1024,50688,131064,124,0,0,0,0],&amp;quot;하&amp;quot;:[19,0,0,0,0,0,6144,12288,12320,12512,12800,14332,12792,12512,259040,127792,12720,12528,12288,12288,12288,12288,4096,0,0],&amp;quot;입&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25584,25368,25368,25496,25072,8256,4096,28800,24960,14720,16128,16128,15616,0,0],&amp;quot;말&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13308,111384,127768,13240,12784,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;했&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,28664,27888,32224,23984,19888,18672,96,13824,15872,13184,15232,28544,50272,0,0],&amp;quot;조&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,8128,3072,1792,16128,28864,25184,1568,1536,50688,131064,124,0,0,0,0],&amp;quot;올&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6528,8064,3840,1536,128768,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;수&amp;quot;:[19,0,0,0,0,0,0,1536,3072,1792,1792,6912,29056,24800,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;록&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,3968,128512,253948,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;열&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,32688,31512,25368,32696,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;단&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,12792,12400,258096,13872,14232,14328,12336,12672,4480,384,384,16256,7936,0,0],&amp;quot;질&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25584,24992,25072,26464,26160,9240,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;히&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25024,26112,26616,25584,25024,25440,25392,25392,25056,24576,24576,24576,8192,8192,0,0],&amp;quot;운&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;데&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22912,23024,22640,24352,22576,23320,23512,23032,22544,18432,16384,16384,24576,0,0],&amp;quot;끌&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;린&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25568,25464,24960,25592,24624,26416,26608,24672,8320,9088,256,256,32512,15872,0,0],&amp;quot;게&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,22960,24000,24512,22624,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;누&amp;quot;:[19,0,0,0,0,0,0,96,192,192,192,15552,16320,960,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;인&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24624,25072,25400,25368,25368,25072,24800,24576,9088,8960,256,256,32512,15872,0,0],&amp;quot;쳐&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,32752,31936,25568,32560,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;문&amp;quot;:[19,0,0,0,0,0,12288,15872,15328,6336,3264,7872,7360,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;몸&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;식&amp;quot;:[19,0,0,0,0,0,12288,24576,24768,24960,25024,24816,24800,25568,26160,9244,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;억&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32536,32536,25368,25072,8416,0,31744,32640,12288,12288,12288,12288,0,0],&amp;quot;전&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25592,24992,31984,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;품&amp;quot;:[19,0,0,0,0,0,12288,16352,7360,8064,7040,16128,26592,127040,253948,18040,1536,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;발&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,111512,127992,13176,13272,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;머&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,25576,25368,32536,27448,25392,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;두&amp;quot;:[19,0,0,0,0,0,0,14336,8128,960,384,12480,16064,5056,192,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;닐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,24624,24624,27696,28464,26608,8288,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;격&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26616,32640,25024,31968,26672,8216,8200,32256,26496,8192,8192,8192,8192,0,0],&amp;quot;없&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25072,32536,31512,25496,25072,8256,8192,13824,15936,4928,15296,28608,51008,0,0],&amp;quot;니&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24584,24624,24624,24624,25648,28208,26544,25328,24576,24576,24576,8192,8192,0,0],&amp;quot;산&amp;quot;:[19,0,0,0,0,0,6144,28672,12352,12480,12480,12496,258272,13280,14128,13852,12288,12672,4480,384,384,16256,7936,0,0],&amp;quot;복&amp;quot;:[19,0,0,0,0,0,14336,12288,28736,15552,14272,16320,7808,3072,130560,249852,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;종&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,26208,1568,130816,116732,280,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;죽&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,24672,122912,262140,18172,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;망&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,78620,258840,13112,12784,12336,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;베&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22912,22912,24448,24448,24456,24536,23024,23024,22776,22648,18456,16384,16384,24576,0,0],&amp;quot;막&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,13084,258840,13080,12784,12464,4096,15872,16320,12288,4096,4096,4096,0,0],&amp;quot;무&amp;quot;:[19,0,0,0,0,0,0,30720,32608,15296,6336,3520,16320,15488,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;릎&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,8128,0,130560,116732,56,16320,3456,8064,3840,16128,14272,0,0],&amp;quot;꿇&amp;quot;:[19,0,0,0,0,0,0,14720,16368,12640,12672,12672,118912,262140,18428,1536,7936,7152,32736,14784,11712,16352,7648,0,0],&amp;quot;까&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,24576,24768,10232,10176,9824,254752,254768,8600,8388,8288,8192,8192,8192,12288,0,0],&amp;quot;놓&amp;quot;:[19,0,0,0,0,0,0,224,192,12480,15552,8128,7552,130048,253948,16760,1792,15872,16320,3584,6912,3328,1792,0,0],&amp;quot;러&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,29056,32720,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;재&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28608,28152,32192,31936,23672,24048,20272,20248,16908,16384,16384,16384,24576,0,0],&amp;quot;받&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,78744,259064,13176,13304,12672,0,7936,3584,768,24960,32640,1920,0,0],&amp;quot;묵&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,3200,130048,118780,1592,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;스&amp;quot;:[19,0,0,0,0,0,0,0,512,1536,3584,1920,1792,7936,12672,24800,0,0,57344,131068,56,0,0,0,0],&amp;quot;릴&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25592,25360,25536,25072,25136,26608,8432,14464,16256,7936,16128,3840,1792,130560,0,0],&amp;quot;계&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,24496,24256,22720,24416,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;구&amp;quot;:[19,0,0,0,0,0,0,6144,16352,12736,12288,12288,12288,6144,4096,131056,67324,1536,1536,1536,1536,1536,512,0,0],&amp;quot;물&amp;quot;:[19,0,0,0,0,0,14336,32544,15328,3264,7872,16064,32768,131040,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;못&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,7360,3776,16064,7296,3072,130816,116732,1048,3584,1536,1792,7936,12480,8304,0,0],&amp;quot;시&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,24816,24800,25520,26424,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;육&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6336,6528,3968,1536,130048,118780,6584,6528,16256,15296,4096,4096,4096,4096,0,0],&amp;quot;신&amp;quot;:[19,0,0,0,0,0,12288,24576,24640,25024,25024,24768,24800,25568,26416,25628,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;일&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25584,25368,25368,25528,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;모&amp;quot;:[19,0,0,0,0,0,0,0,14336,32256,16352,6368,3264,7872,16064,6144,2048,1024,50688,131064,124,0,0,0,0],&amp;quot;행&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,20472,27888,32224,32176,23984,19696,25184,15872,30208,25344,25344,16128,7680,0,0],&amp;quot;절&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,32160,32752,26464,26160,9752,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;멸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,32760,31512,25400,32688,25072,8224,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;끊&amp;quot;:[19,0,0,0,0,0,0,14592,16368,13280,12672,12672,12672,114688,262140,16636,7168,6240,32352,15456,13408,16224,7648,0,0],&amp;quot;순&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,131008,116732,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;간&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,13304,12688,258240,12384,12336,12312,12296,12672,4480,384,384,16256,7936,0,0]};&lt;br /&gt;
    var BODY_SCALE = 1;&lt;br /&gt;
    var TITLE_SCALE = 2;&lt;br /&gt;
    var BODY_TRACKING = -2;&lt;br /&gt;
    var TITLE_TRACKING = 0;&lt;br /&gt;
    var SPACE_ADVANCE = Math.round(CELL_H * 0.5);&lt;br /&gt;
    var BODY_LINE_HEIGHT = CELL_H + 8;&lt;br /&gt;
    var PARAGRAPH_GAP = 20;&lt;br /&gt;
    var DIVIDER_Y = 52;&lt;br /&gt;
    var BODY_TOP = 68;&lt;br /&gt;
    var BODY_COLOR = &#039;#d8d8d8&#039;;&lt;br /&gt;
    var TITLE_COLOR = &#039;#ffffff&#039;;&lt;br /&gt;
    var DIVIDER_COLOR = &#039;#555555&#039;;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var observed = typeof WeakSet === &#039;function&#039; ? new WeakSet() : null;&lt;br /&gt;
    var resizeObserver = typeof ResizeObserver === &#039;function&#039; ? new ResizeObserver(function (entries) {&lt;br /&gt;
        entries.forEach(function (entry) { schedule(entry.target, false); });&lt;br /&gt;
    }) : null;&lt;br /&gt;
&lt;br /&gt;
    function glyphFor(character) {&lt;br /&gt;
        return Object.prototype.hasOwnProperty.call(GLYPHS, character) ? GLYPHS[character] : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function measureText(text, scale, tracking) {&lt;br /&gt;
        var width = 0;&lt;br /&gt;
        var glyphCount = 0;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                width += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            width += glyph[0] * scale;&lt;br /&gt;
            width += tracking * scale;&lt;br /&gt;
            glyphCount += 1;&lt;br /&gt;
        });&lt;br /&gt;
        if (glyphCount &amp;amp;&amp;amp; tracking) width -= tracking * scale;&lt;br /&gt;
        return Math.max(0, Math.round(width));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function breakLongToken(token, maxWidth) {&lt;br /&gt;
        var pieces = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
        Array.from(token).forEach(function (character) {&lt;br /&gt;
            var candidate = current + character;&lt;br /&gt;
            if (current &amp;amp;&amp;amp; measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;gt; maxWidth) {&lt;br /&gt;
                pieces.push(current);&lt;br /&gt;
                current = character;&lt;br /&gt;
            } else {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        if (current) pieces.push(current);&lt;br /&gt;
        return pieces;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wrapParagraph(text, maxWidth) {&lt;br /&gt;
        var words = String(text || &#039;&#039;).trim().split(/\s+/).filter(Boolean);&lt;br /&gt;
        var lines = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        words.forEach(function (word) {&lt;br /&gt;
            var candidate = current ? current + &#039; &#039; + word : word;&lt;br /&gt;
            var pieces;&lt;br /&gt;
            if (measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (current) {&lt;br /&gt;
                lines.push(current);&lt;br /&gt;
                current = &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (measureText(word, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = word;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            pieces = breakLongToken(word, maxWidth);&lt;br /&gt;
            pieces.forEach(function (piece, index) {&lt;br /&gt;
                if (index === pieces.length - 1) current = piece;&lt;br /&gt;
                else lines.push(piece);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        if (current) lines.push(current);&lt;br /&gt;
        return lines.length ? lines : [&#039;&#039;];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateOpticalShift(lines, width) {&lt;br /&gt;
        var weightedCenter = 0;&lt;br /&gt;
        var totalWeight = 0;&lt;br /&gt;
        var maxLineWidth = 0;&lt;br /&gt;
        var shift;&lt;br /&gt;
        lines.forEach(function (line) {&lt;br /&gt;
            var lineWidth = line.width;&lt;br /&gt;
            var weight = Math.max(1, lineWidth) * BODY_LINE_HEIGHT;&lt;br /&gt;
            weightedCenter += (lineWidth / 2) * weight;&lt;br /&gt;
            totalWeight += weight;&lt;br /&gt;
            maxLineWidth = Math.max(maxLineWidth, lineWidth);&lt;br /&gt;
        });&lt;br /&gt;
        if (!totalWeight) return 0;&lt;br /&gt;
        shift = Math.round((width / 2) - (weightedCenter / totalWeight));&lt;br /&gt;
        return Math.max(0, Math.min(Math.max(0, width - maxLineWidth), shift));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawGlyph(context, glyph, x, y, scale) {&lt;br /&gt;
        var py;&lt;br /&gt;
        var px;&lt;br /&gt;
        var row;&lt;br /&gt;
        for (py = 0; py &amp;lt; CELL_H; py += 1) {&lt;br /&gt;
            row = glyph[py + 1] || 0;&lt;br /&gt;
            if (!row) continue;&lt;br /&gt;
            for (px = 0; px &amp;lt; CELL_W; px += 1) {&lt;br /&gt;
                if (row &amp;amp; (1 &amp;lt;&amp;lt; px)) {&lt;br /&gt;
                    context.fillRect(&lt;br /&gt;
                        Math.round(x + px * scale),&lt;br /&gt;
                        Math.round(y + py * scale),&lt;br /&gt;
                        scale,&lt;br /&gt;
                        scale&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawText(context, text, x, y, scale, tracking, color) {&lt;br /&gt;
        context.fillStyle = color;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                x += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            drawGlyph(context, glyph, x, y, scale);&lt;br /&gt;
            x += (glyph[0] + tracking) * scale;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sourceContent(inner) {&lt;br /&gt;
        var titleNode = inner.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        var paragraphs = [];&lt;br /&gt;
        Array.prototype.forEach.call(inner.children || [], function (child) {&lt;br /&gt;
            if (child &amp;amp;&amp;amp; child.tagName === &#039;P&#039;) {&lt;br /&gt;
                var value = String(child.textContent || &#039;&#039;).trim();&lt;br /&gt;
                if (value) paragraphs.push(value);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        return {&lt;br /&gt;
            title: titleNode ? String(titleNode.textContent || &#039;&#039;).trim() : &#039;&#039;,&lt;br /&gt;
            paragraphs: paragraphs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureCanvas(inner) {&lt;br /&gt;
        var canvas = inner.querySelector(&#039;.main-manifesto-bitmap&#039;);&lt;br /&gt;
        if (!canvas) {&lt;br /&gt;
            canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            canvas.className = &#039;main-manifesto-bitmap&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            canvas.setAttribute(&#039;data-bitmap-version&#039;, VERSION);&lt;br /&gt;
            inner.insertBefore(canvas, inner.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
        return canvas;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(inner, force) {&lt;br /&gt;
        var width;&lt;br /&gt;
        var source;&lt;br /&gt;
        var paragraphs;&lt;br /&gt;
        var flatLines = [];&lt;br /&gt;
        var titleWidth;&lt;br /&gt;
        var height;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var context;&lt;br /&gt;
        var y;&lt;br /&gt;
        var opticalShift;&lt;br /&gt;
&lt;br /&gt;
        if (!inner || !inner.isConnected) return;&lt;br /&gt;
        width = Math.max(1, Math.floor(inner.clientWidth || inner.getBoundingClientRect().width || 0));&lt;br /&gt;
        if (width &amp;lt;= 1) {&lt;br /&gt;
            window.setTimeout(function () { schedule(inner, true); }, 60);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (!force &amp;amp;&amp;amp; Number(inner.getAttribute(&#039;data-bitmap-width&#039;)) === width &amp;amp;&amp;amp; inner.classList.contains(&#039;is-bitmap-ready&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        source = sourceContent(inner);&lt;br /&gt;
        if (!source.title || !source.paragraphs.length) {&lt;br /&gt;
            fail(inner, new Error(&#039;Manifesto source nodes were not found.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        paragraphs = source.paragraphs.map(function (paragraph) {&lt;br /&gt;
            return wrapParagraph(paragraph, width).map(function (line) {&lt;br /&gt;
                var item = { text:line, width:measureText(line, BODY_SCALE, BODY_TRACKING) };&lt;br /&gt;
                flatLines.push(item);&lt;br /&gt;
                return item;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        titleWidth = measureText(source.title, TITLE_SCALE, TITLE_TRACKING);&lt;br /&gt;
        height = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, index) {&lt;br /&gt;
            height += lines.length * BODY_LINE_HEIGHT;&lt;br /&gt;
            if (index &amp;lt; paragraphs.length - 1) height += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
        height = Math.max(height, DIVIDER_Y + 2);&lt;br /&gt;
&lt;br /&gt;
        canvas = ensureCanvas(inner);&lt;br /&gt;
        canvas.width = width;&lt;br /&gt;
        canvas.height = Math.ceil(height);&lt;br /&gt;
        canvas.style.width = width + &#039;px&#039;;&lt;br /&gt;
        canvas.style.height = Math.ceil(height) + &#039;px&#039;;&lt;br /&gt;
        context = canvas.getContext(&#039;2d&#039;, { alpha:true });&lt;br /&gt;
        if (!context) {&lt;br /&gt;
            fail(inner, new Error(&#039;Canvas 2D context is unavailable.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        context.imageSmoothingEnabled = false;&lt;br /&gt;
        context.clearRect(0, 0, canvas.width, canvas.height);&lt;br /&gt;
&lt;br /&gt;
        drawText(&lt;br /&gt;
            context,&lt;br /&gt;
            source.title,&lt;br /&gt;
            Math.round((width - titleWidth) / 2),&lt;br /&gt;
            0,&lt;br /&gt;
            TITLE_SCALE,&lt;br /&gt;
            TITLE_TRACKING,&lt;br /&gt;
            TITLE_COLOR&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /* 완성형 구분선은 폐기했다. 실선 애니메이션은 ManifestoIntro만 담당한다. */&lt;br /&gt;
&lt;br /&gt;
        opticalShift = calculateOpticalShift(flatLines, width);&lt;br /&gt;
        y = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, paragraphIndex) {&lt;br /&gt;
            lines.forEach(function (line) {&lt;br /&gt;
                drawText(context, line.text, opticalShift, y, BODY_SCALE, BODY_TRACKING, BODY_COLOR);&lt;br /&gt;
                y += BODY_LINE_HEIGHT;&lt;br /&gt;
            });&lt;br /&gt;
            if (paragraphIndex &amp;lt; paragraphs.length - 1) y += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-width&#039;, String(width));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-optical-shift-x&#039;, String(opticalShift));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        inner.classList.add(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fail(inner, error) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;failed&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-error&#039;, String(error &amp;amp;&amp;amp; error.message ? error.message : error));&lt;br /&gt;
        try { console.error(&#039;[MainPageBitmap]&#039;, error); } catch (ignore) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(inner, force) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            try { render(inner, force); }&lt;br /&gt;
            catch (error) { fail(inner, error); }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mount(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var nodes = [];&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(SELECTOR)) nodes.push(scope);&lt;br /&gt;
        nodes = nodes.concat(Array.prototype.slice.call(scope.querySelectorAll(SELECTOR)));&lt;br /&gt;
        nodes.forEach(function (inner) {&lt;br /&gt;
            if (resizeObserver &amp;amp;&amp;amp; (!observed || !observed.has(inner))) {&lt;br /&gt;
                resizeObserver.observe(inner);&lt;br /&gt;
                if (observed) observed.add(inner);&lt;br /&gt;
            }&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function recalculate() {&lt;br /&gt;
        Array.prototype.forEach.call(document.querySelectorAll(SELECTOR), function (inner) {&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot() {&lt;br /&gt;
        mount(document);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 120);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) document.addEventListener(&#039;DOMContentLoaded&#039;, boot, { once:true });&lt;br /&gt;
    else boot();&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        window.clearTimeout(resizeTimer);&lt;br /&gt;
        resizeTimer = window.setTimeout(recalculate, 100);&lt;br /&gt;
    }, { passive:true });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                mount(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        recalculate:recalculate,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            var inner = document.querySelector(SELECTOR);&lt;br /&gt;
            return inner ? {&lt;br /&gt;
                integrated:true,&lt;br /&gt;
                state:inner.getAttribute(&#039;data-bitmap-state&#039;) || &#039;pending&#039;,&lt;br /&gt;
                error:inner.getAttribute(&#039;data-bitmap-error&#039;) || &#039;&#039;,&lt;br /&gt;
                width:inner.getAttribute(&#039;data-bitmap-width&#039;) || &#039;&#039;,&lt;br /&gt;
                opticalShiftX:inner.getAttribute(&#039;data-bitmap-optical-shift-x&#039;) || &#039;&#039;,&lt;br /&gt;
                canvas:!!inner.querySelector(&#039;.main-manifesto-bitmap&#039;)&lt;br /&gt;
            } : { integrated:true, state:&#039;not-mounted&#039; };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    window.MainPageManifesto = window.MainPageManifesto || {};&lt;br /&gt;
    window.MainPageManifesto.recalculate = recalculate;&lt;br /&gt;
})(window, document, window.mw);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4654</id>
		<title>미디어위키:Common.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4654"/>
		<updated>2026-07-17T00:46:14Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-separated-outline-effects-20260717 / Common.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
   COASTLINE: BLACK ICE - Common (Entry Point)&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css는 CSS 로더 역할만 한다.&lt;br /&gt;
&lt;br /&gt;
정리 원칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 공통 토큰과 전역 테마: Theme.css&lt;br /&gt;
- 틀과 문서 컴포넌트: Template.css, Components.css&lt;br /&gt;
- 아이콘: Icons.css&lt;br /&gt;
- 레이아웃: Layout.css&lt;br /&gt;
- 편집기: Editor.css&lt;br /&gt;
- 다이얼로그: Dialogs.css&lt;br /&gt;
- 좌측 언어 다이얼: Layout.css + Common.js&lt;br /&gt;
- 대문 전용: MainPage.css&lt;br /&gt;
- 테스트 전용: Test.css&lt;br /&gt;
&lt;br /&gt;
주의:&lt;br /&gt;
Test.css는 실험용이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
@import url(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Template.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-era-page-hotfix-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Icons.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Layout.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Theme.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260712-yisunshin-galmuri-method-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Editor.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Components.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Dialogs.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:MainPage.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260717-portal-section-css-top-cover-044&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:DevTools.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Nation_List_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix57&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:History_Event_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4653</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4653"/>
		<updated>2026-07-17T00:41:51Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-cover80-layered-frame-20260717 / MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:36px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
설정 왼쪽부터 기업 및 공동체 오른쪽까지의 실제 SVG 경계를 읽어&lt;br /&gt;
중앙 프레임의 최외곽 폭으로 사용한다. 좌우 투명 영역은 새 프레임으로&lt;br /&gt;
채우지 않는다. 형상은 위가 넓고 아래가 좁아지도록 뒤집고, 기존 상단&lt;br /&gt;
프레임 하단 9px 안으로 겹쳐 하나로 접합한다.&lt;br /&gt;
좌우 경사는 기존 설정·기업 버튼 사선의 화면상 기울기를 그대로 연장한다.&lt;br /&gt;
외곽·프레임 면·우물·버튼의 x 좌표도 같은 직선식으로 투영해 전 층의&lt;br /&gt;
사선이 서로 평행하고 끊김 없이 이어지게 한다.&lt;br /&gt;
두 프레임이 실제로 포개지는 상단 8px에서는 #050505를 #1d1d1d 면으로&lt;br /&gt;
덮는다. 좌우 방향선은 외곽선에서 재계산하지 않고 기존 설정 버튼의 좌측&lt;br /&gt;
인셋선과 기업 버튼의 우측 인셋선을 직접 읽어 같은 직선으로 연장한다.&lt;br /&gt;
&lt;br /&gt;
초기 길라잡이 규격:&lt;br /&gt;
- 버튼 외곽 22px&lt;br /&gt;
- 프레임 실색 #1d1d1d 7px&lt;br /&gt;
- 우물 간격 2px&lt;br /&gt;
- 전체 부착 행 42px&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:10%;&lt;br /&gt;
    right:auto;&lt;br /&gt;
    width:80%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-svg {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 42px 본체·광원 + 상하 1px #050505 외곽선으로 44px이다.&lt;br /&gt;
   상하 2px #080808 간격, 상단 9px·하단 8px 프레임을 포함한 전체 행은 65px이다.&lt;br /&gt;
   상단 9px 중 외곽선·광원선을 제외한 #1d1d1d 실색 영역은 7px이다. */&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단 총 36px:&lt;br /&gt;
4px 프레임 + 2px #080808 + 24px 버튼 + 2px #080808 + 4px 프레임&lt;br /&gt;
&lt;br /&gt;
하단 총 65px:&lt;br /&gt;
중앙에서 위로 솟은 패널 하단 프레임 + 2px #080808&lt;br /&gt;
+ 44px 길라잡이 외곽 + 2px #080808 + 공유 하단 프레임&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:24px !important;&lt;br /&gt;
    min-height:24px !important;&lt;br /&gt;
    max-height:24px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:22px !important;&lt;br /&gt;
    min-height:22px !important;&lt;br /&gt;
    max-height:22px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4652</id>
		<title>미디어위키:Common.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4652"/>
		<updated>2026-07-17T00:41:49Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-cover80-layered-frame-20260717 / Common.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
   COASTLINE: BLACK ICE - Common (Entry Point)&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css는 CSS 로더 역할만 한다.&lt;br /&gt;
&lt;br /&gt;
정리 원칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 공통 토큰과 전역 테마: Theme.css&lt;br /&gt;
- 틀과 문서 컴포넌트: Template.css, Components.css&lt;br /&gt;
- 아이콘: Icons.css&lt;br /&gt;
- 레이아웃: Layout.css&lt;br /&gt;
- 편집기: Editor.css&lt;br /&gt;
- 다이얼로그: Dialogs.css&lt;br /&gt;
- 좌측 언어 다이얼: Layout.css + Common.js&lt;br /&gt;
- 대문 전용: MainPage.css&lt;br /&gt;
- 테스트 전용: Test.css&lt;br /&gt;
&lt;br /&gt;
주의:&lt;br /&gt;
Test.css는 실험용이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
@import url(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Template.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-era-page-hotfix-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Icons.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Layout.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Theme.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260712-yisunshin-galmuri-method-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Editor.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Components.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Dialogs.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:MainPage.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260717-portal-section-css-top-cover-043&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:DevTools.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Nation_List_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix57&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:History_Event_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4651</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4651"/>
		<updated>2026-07-17T00:37:55Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-cover80-frame-border-20260717 / MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:36px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
설정 왼쪽부터 기업 및 공동체 오른쪽까지의 실제 SVG 경계를 읽어&lt;br /&gt;
중앙 프레임의 최외곽 폭으로 사용한다. 좌우 투명 영역은 새 프레임으로&lt;br /&gt;
채우지 않는다. 형상은 위가 넓고 아래가 좁아지도록 뒤집고, 기존 상단&lt;br /&gt;
프레임 하단 9px 안으로 겹쳐 하나로 접합한다.&lt;br /&gt;
좌우 경사는 기존 설정·기업 버튼 사선의 화면상 기울기를 그대로 연장한다.&lt;br /&gt;
외곽·프레임 면·우물·버튼의 x 좌표도 같은 직선식으로 투영해 전 층의&lt;br /&gt;
사선이 서로 평행하고 끊김 없이 이어지게 한다.&lt;br /&gt;
두 프레임이 실제로 포개지는 상단 8px에서는 #050505를 #1d1d1d 면으로&lt;br /&gt;
덮는다. 좌우 방향선은 외곽선에서 재계산하지 않고 기존 설정 버튼의 좌측&lt;br /&gt;
인셋선과 기업 버튼의 우측 인셋선을 직접 읽어 같은 직선으로 연장한다.&lt;br /&gt;
&lt;br /&gt;
초기 길라잡이 규격:&lt;br /&gt;
- 버튼 외곽 22px&lt;br /&gt;
- 프레임 실색 #1d1d1d 7px&lt;br /&gt;
- 우물 간격 2px&lt;br /&gt;
- 전체 부착 행 42px&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:10%;&lt;br /&gt;
    right:auto;&lt;br /&gt;
    width:80%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-svg {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 42px 본체·광원 + 상하 1px #050505 외곽선으로 44px이다.&lt;br /&gt;
   상하 2px #080808 간격, 상단 9px·하단 8px 프레임을 포함한 전체 행은 65px이다.&lt;br /&gt;
   상단 9px 중 외곽선·광원선을 제외한 #1d1d1d 실색 영역은 7px이다. */&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단 총 36px:&lt;br /&gt;
4px 프레임 + 2px #080808 + 24px 버튼 + 2px #080808 + 4px 프레임&lt;br /&gt;
&lt;br /&gt;
하단 총 65px:&lt;br /&gt;
중앙에서 위로 솟은 패널 하단 프레임 + 2px #080808&lt;br /&gt;
+ 44px 길라잡이 외곽 + 2px #080808 + 공유 하단 프레임&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:24px !important;&lt;br /&gt;
    min-height:24px !important;&lt;br /&gt;
    max-height:24px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:22px !important;&lt;br /&gt;
    min-height:22px !important;&lt;br /&gt;
    max-height:22px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4650</id>
		<title>미디어위키:Common.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4650"/>
		<updated>2026-07-17T00:37:53Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-cover80-frame-border-20260717 / Common.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
   COASTLINE: BLACK ICE - Common (Entry Point)&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css는 CSS 로더 역할만 한다.&lt;br /&gt;
&lt;br /&gt;
정리 원칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 공통 토큰과 전역 테마: Theme.css&lt;br /&gt;
- 틀과 문서 컴포넌트: Template.css, Components.css&lt;br /&gt;
- 아이콘: Icons.css&lt;br /&gt;
- 레이아웃: Layout.css&lt;br /&gt;
- 편집기: Editor.css&lt;br /&gt;
- 다이얼로그: Dialogs.css&lt;br /&gt;
- 좌측 언어 다이얼: Layout.css + Common.js&lt;br /&gt;
- 대문 전용: MainPage.css&lt;br /&gt;
- 테스트 전용: Test.css&lt;br /&gt;
&lt;br /&gt;
주의:&lt;br /&gt;
Test.css는 실험용이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
@import url(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Template.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-era-page-hotfix-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Icons.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Layout.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Theme.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260712-yisunshin-galmuri-method-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Editor.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Components.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Dialogs.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:MainPage.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260717-portal-section-css-top-cover-042&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:DevTools.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Nation_List_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix57&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:History_Event_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4649</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4649"/>
		<updated>2026-07-17T00:34:39Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-css-top-cover-80-border-20260717 / MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:36px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
설정 왼쪽부터 기업 및 공동체 오른쪽까지의 실제 SVG 경계를 읽어&lt;br /&gt;
중앙 프레임의 최외곽 폭으로 사용한다. 좌우 투명 영역은 새 프레임으로&lt;br /&gt;
채우지 않는다. 형상은 위가 넓고 아래가 좁아지도록 뒤집고, 기존 상단&lt;br /&gt;
프레임 하단 9px 안으로 겹쳐 하나로 접합한다.&lt;br /&gt;
좌우 경사는 기존 설정·기업 버튼 사선의 화면상 기울기를 그대로 연장한다.&lt;br /&gt;
외곽·프레임 면·우물·버튼의 x 좌표도 같은 직선식으로 투영해 전 층의&lt;br /&gt;
사선이 서로 평행하고 끊김 없이 이어지게 한다.&lt;br /&gt;
두 프레임이 실제로 포개지는 상단 8px에서는 #050505를 #1d1d1d 면으로&lt;br /&gt;
덮는다. 좌우 방향선은 외곽선에서 재계산하지 않고 기존 설정 버튼의 좌측&lt;br /&gt;
인셋선과 기업 버튼의 우측 인셋선을 직접 읽어 같은 직선으로 연장한다.&lt;br /&gt;
&lt;br /&gt;
초기 길라잡이 규격:&lt;br /&gt;
- 버튼 외곽 22px&lt;br /&gt;
- 프레임 실색 #1d1d1d 7px&lt;br /&gt;
- 우물 간격 2px&lt;br /&gt;
- 전체 부착 행 42px&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:10%;&lt;br /&gt;
    right:auto;&lt;br /&gt;
    width:80%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-svg {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 42px 본체·광원 + 상하 1px #050505 외곽선으로 44px이다.&lt;br /&gt;
   상하 2px #080808 간격, 상단 9px·하단 8px 프레임을 포함한 전체 행은 65px이다.&lt;br /&gt;
   상단 9px 중 외곽선·광원선을 제외한 #1d1d1d 실색 영역은 7px이다. */&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단 총 36px:&lt;br /&gt;
4px 프레임 + 2px #080808 + 24px 버튼 + 2px #080808 + 4px 프레임&lt;br /&gt;
&lt;br /&gt;
하단 총 65px:&lt;br /&gt;
중앙에서 위로 솟은 패널 하단 프레임 + 2px #080808&lt;br /&gt;
+ 44px 길라잡이 외곽 + 2px #080808 + 공유 하단 프레임&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:24px !important;&lt;br /&gt;
    min-height:24px !important;&lt;br /&gt;
    max-height:24px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:22px !important;&lt;br /&gt;
    min-height:22px !important;&lt;br /&gt;
    max-height:22px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4648</id>
		<title>미디어위키:Common.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4648"/>
		<updated>2026-07-17T00:34:38Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-css-top-cover-80-border-20260717 / Common.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
   COASTLINE: BLACK ICE - Common (Entry Point)&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css는 CSS 로더 역할만 한다.&lt;br /&gt;
&lt;br /&gt;
정리 원칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 공통 토큰과 전역 테마: Theme.css&lt;br /&gt;
- 틀과 문서 컴포넌트: Template.css, Components.css&lt;br /&gt;
- 아이콘: Icons.css&lt;br /&gt;
- 레이아웃: Layout.css&lt;br /&gt;
- 편집기: Editor.css&lt;br /&gt;
- 다이얼로그: Dialogs.css&lt;br /&gt;
- 좌측 언어 다이얼: Layout.css + Common.js&lt;br /&gt;
- 대문 전용: MainPage.css&lt;br /&gt;
- 테스트 전용: Test.css&lt;br /&gt;
&lt;br /&gt;
주의:&lt;br /&gt;
Test.css는 실험용이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
@import url(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Template.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-era-page-hotfix-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Icons.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Layout.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Theme.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260712-yisunshin-galmuri-method-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Editor.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Components.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Dialogs.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:MainPage.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260717-portal-section-css-top-cover-041&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:DevTools.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Nation_List_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix57&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:History_Event_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4647</id>
		<title>미디어위키:MainPage.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:MainPage.css&amp;diff=4647"/>
		<updated>2026-07-17T00:25:10Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-css-top-cover-20260717 / MainPage.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
COASTLINE: BLACK ICE - MainPage&lt;br /&gt;
로고 + 카테고리 네비 + 선언문 대문&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
책임 범위&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 대문 본문 .main-portal 내부만 담당한다.&lt;br /&gt;
- 사이트 공통 셸은 Layout.css가 담당한다.&lt;br /&gt;
- 카테고리 네비의 SVG 구조는 CategoryNav.js가 생성한다.&lt;br /&gt;
- 콘솔, 그래프, STATUS, INFORMATION, PROJECTS는 대문에서 제거한다.&lt;br /&gt;
- 「진창」 선언문 배치는 이 파일이 담당하며, 폰트 선언은 Theme.css가 담당한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal {&lt;br /&gt;
    --panel:#1d1d1d;&lt;br /&gt;
    --well:#080808;&lt;br /&gt;
    --edge:#050505;&lt;br /&gt;
    --highlight:#555555;&lt;br /&gt;
    --shadow:#101010;&lt;br /&gt;
    --text:#e2e2e2;&lt;br /&gt;
    --text-strong:#ffffff;&lt;br /&gt;
    --block-gap:2px;&lt;br /&gt;
    --frame:8px;&lt;br /&gt;
    --manifesto-divider-gap:8px;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    min-height:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    color:var(--text);&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    line-height:18px;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal *,&lt;br /&gt;
.main-portal *::before,&lt;br /&gt;
.main-portal *::after {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal a:visited {&lt;br /&gt;
    color:var(--text) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a:hover,&lt;br /&gt;
.main-portal a:focus {&lt;br /&gt;
    color:var(--text-strong) !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 과거 대문 구조가 캐시에 남아 있어도 화면에는 나타나지 않게 한다. */&lt;br /&gt;
.main-portal &amp;gt; .console,&lt;br /&gt;
.main-portal &amp;gt; .portal-bottom-row,&lt;br /&gt;
.main-portal &amp;gt; [data-archive-graph] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
공통 돌출 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    background:var(--panel, #1d1d1d);&lt;br /&gt;
    border:1px solid var(--edge, #050505);&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--highlight, #555555),&lt;br /&gt;
        inset -1px 0 0 var(--highlight, #555555),&lt;br /&gt;
        inset 1px 0 0 var(--shadow, #101010),&lt;br /&gt;
        inset 0 -1px 0 var(--shadow, #101010);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    margin:0 0 var(--block-gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
로고 프레임&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.main-portal .lab-logo {&lt;br /&gt;
    padding:var(--frame);&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well {&lt;br /&gt;
    min-height:86px;&lt;br /&gt;
    padding:10px 8px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well);&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 var(--shadow),&lt;br /&gt;
        inset -1px 0 0 var(--shadow),&lt;br /&gt;
        inset 1px 0 0 var(--highlight),&lt;br /&gt;
        inset 0 -1px 0 var(--highlight);&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well a,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-description {&lt;br /&gt;
    display:inline-block;&lt;br /&gt;
    line-height:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-logo-well img,&lt;br /&gt;
.main-portal .portal-logo-well .mw-file-element {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:1000px;&lt;br /&gt;
    max-width:144%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    filter:grayscale(1) contrast(1.04) brightness(1.02);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
카테고리 네비&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
.portal-category-nav {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:36px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
    border:0;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-category-svg {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
각 버튼은 SVG 안에서 독립된 사선 프레임으로 그린다.&lt;br /&gt;
- 외곽 1px: #050505&lt;br /&gt;
- 본체: #1d1d1d&lt;br /&gt;
- 상단/우측 inset: #555555&lt;br /&gt;
- 좌측/하단 inset: #101010&lt;br /&gt;
- 버튼 사이 배경: #080808 2px&lt;br /&gt;
*/&lt;br /&gt;
.portal-cat-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-top,&lt;br /&gt;
.portal-cat-inset-right {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-inset-left {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-hit {&lt;br /&gt;
    fill:transparent;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-surface,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-surface {&lt;br /&gt;
    fill:#242424;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-surface {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-top,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-right {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-bottom,&lt;br /&gt;
.portal-cat-anchor:active .portal-cat-inset-left {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    text-anchor:middle;&lt;br /&gt;
    dominant-baseline:middle;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    paint-order:stroke;&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    stroke-linejoin:round;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.portal-cat-anchor:hover .portal-cat-label,&lt;br /&gt;
.portal-cat-anchor:focus .portal-cat-label {&lt;br /&gt;
    fill:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal a,&lt;br /&gt;
.main-portal .portal-cat-surface,&lt;br /&gt;
.main-portal .portal-cat-inset,&lt;br /&gt;
.main-portal .portal-cat-label {&lt;br /&gt;
    transition:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:820px) {&lt;br /&gt;
    .portal-cat-label {&lt;br /&gt;
        font-size:11px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:620px) {&lt;br /&gt;
    .main-portal {&lt;br /&gt;
        --frame:6px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 / 실제 픽셀 웹폰트 단일 구현&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
&lt;br /&gt;
@font-face {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039;;&lt;br /&gt;
    src:url(&#039;/fonts/NanumMyeongjoBoldPixel18.woff2?v=20260713-perfect-bottom-direct-002&#039;) format(&#039;woff2&#039;);&lt;br /&gt;
    font-weight:400;&lt;br /&gt;
    font-style:normal;&lt;br /&gt;
    font-display:block;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:10px 0 24px;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    align-items:flex-start;&lt;br /&gt;
    justify-content:center;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:0 !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    min-width:660px !important;&lt;br /&gt;
    max-width:660px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-inner::before,&lt;br /&gt;
.main-portal .main-manifesto-inner::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    background:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:660px !important;&lt;br /&gt;
    height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    min-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    max-height:calc(48px + var(--manifesto-divider-gap) + 1px) !important;&lt;br /&gt;
    margin:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    padding:0 0 var(--manifesto-divider-gap) !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 구분선은 ManifestoIntro가 만드는 좌우 실선 두 개만 사용한다. */&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:36px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:48px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    text-align:center !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    min-width:620px !important;&lt;br /&gt;
    max-width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 auto !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    word-break:keep-all !important;&lt;br /&gt;
    overflow-wrap:break-word !important;&lt;br /&gt;
    text-align:left !important;&lt;br /&gt;
    text-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy,&lt;br /&gt;
.main-portal .main-manifesto-copy * {&lt;br /&gt;
    font-family:&#039;NanumMyeongjoBold Pixel 18&#039; !important;&lt;br /&gt;
    font-size:18px !important;&lt;br /&gt;
    font-weight:400 !important;&lt;br /&gt;
    font-style:normal !important;&lt;br /&gt;
    font-synthesis:none !important;&lt;br /&gt;
    font-kerning:none !important;&lt;br /&gt;
    font-variant-ligatures:none !important;&lt;br /&gt;
    font-feature-settings:&#039;kern&#039; 0, &#039;liga&#039; 0 !important;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    letter-spacing:0 !important;&lt;br /&gt;
    color:#e2e2e2 !important;&lt;br /&gt;
    -webkit-text-fill-color:#e2e2e2 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p {&lt;br /&gt;
    position:static !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    width:620px !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 0 10px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    clip:auto !important;&lt;br /&gt;
    clip-path:none !important;&lt;br /&gt;
    white-space:normal !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    user-select:text !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy p:last-child {&lt;br /&gt;
    margin-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-bitmap,&lt;br /&gt;
.main-portal canvas.main-manifesto-bitmap {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이는 시대의 대륙 패널처럼 본문 DOM을 유지한 채 화면만 전환한다.&lt;br /&gt;
현재 단계에서는 왼쪽 더미 메뉴로 진입 애니메이션과 버튼 디자인만 확인한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen {&lt;br /&gt;
    flex:1 1 0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:transparent;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-guide-screen[hidden],&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto[hidden] {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:10px;&lt;br /&gt;
    left:10px;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    gap:2px;&lt;br /&gt;
    z-index:2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-item {&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateX(-240px);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
    animation:main-guide-menu-enter 260ms cubic-bezier(.16,.84,.24,1) both;&lt;br /&gt;
    animation-delay:calc(var(--guide-menu-order) * 44ms);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@keyframes main-guide-menu-enter {&lt;br /&gt;
    0% {&lt;br /&gt;
        opacity:0;&lt;br /&gt;
        transform:translateX(-240px);&lt;br /&gt;
    }&lt;br /&gt;
    72% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(4px);&lt;br /&gt;
    }&lt;br /&gt;
    100% {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:translateX(0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button {&lt;br /&gt;
    appearance:none;&lt;br /&gt;
    -webkit-appearance:none;&lt;br /&gt;
    display:grid;&lt;br /&gt;
    grid-template-columns:30px minmax(0,1fr) 14px;&lt;br /&gt;
    align-items:center;&lt;br /&gt;
    width:230px;&lt;br /&gt;
    height:30px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
    font-family:inherit;&lt;br /&gt;
    font-size:12px;&lt;br /&gt;
    font-weight:700;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:left;&lt;br /&gt;
    text-shadow:1px 1px 0 #050505;&lt;br /&gt;
    cursor:pointer;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-number {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:30px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    border-right:1px solid #050505;&lt;br /&gt;
    box-shadow:inset 0 1px 0 #242424;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-label {&lt;br /&gt;
    display:block;&lt;br /&gt;
    min-width:0;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    padding:0 8px;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    line-height:28px;&lt;br /&gt;
    white-space:nowrap;&lt;br /&gt;
    text-overflow:ellipsis;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-arrow {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:14px;&lt;br /&gt;
    height:28px;&lt;br /&gt;
    color:#8f8f8f;&lt;br /&gt;
    line-height:27px;&lt;br /&gt;
    text-align:center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
    background:#242424;&lt;br /&gt;
    outline:0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-number,&lt;br /&gt;
.main-portal .main-guide-menu-button:hover .main-guide-menu-arrow,&lt;br /&gt;
.main-portal .main-guide-menu-button:focus-visible .main-guide-menu-arrow {&lt;br /&gt;
    color:#ffffff;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-guide-menu-button:active {&lt;br /&gt;
    color:#e2e2e2;&lt;br /&gt;
    background:#101010;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal .main-guide-screen.is-guide-menu-entering .main-guide-menu-item {&lt;br /&gt;
        animation:none;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 중앙 「프로젝트」 기둥 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
버튼 2개 - #1d1d1d 기둥 - 버튼 2개.&lt;br /&gt;
&lt;br /&gt;
CategoryPillar.js가 기존 project 버튼의 좌우 사선과 2px 간격을 그대로 사용한다.&lt;br /&gt;
기둥은 같은 경사를 유지한 채 SVG의 위·아래로 3px 우물을 관통하며 외곽 프레임과 같은&lt;br /&gt;
#1d1d1d 면에 직접 닿는다. 상하 외곽선은 두지 않아 하나의 패널로&lt;br /&gt;
이어진다. 버튼과 접하는 좌우의 #050505 외곽선은 생략하되,&lt;br /&gt;
기존 안쪽 방향성 광원선을 그 외곽선 좌표로 한 칸 이동한다.&lt;br /&gt;
안쪽선이 있던 자리는 별도 선 없이 #1d1d1d 본체 면으로 돌아간다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-cat-anchor[data-category-key=&amp;quot;project&amp;quot;],&lt;br /&gt;
.main-portal .portal-category-svg.has-category-pillar&lt;br /&gt;
.portal-category-pillar-source {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar {&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-line {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-category-pillar-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
    cursor:default;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
상단 카테고리 하단 회전·접합형 작품 버튼 / 20260716&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
설정 왼쪽부터 기업 및 공동체 오른쪽까지의 실제 SVG 경계를 읽어&lt;br /&gt;
중앙 프레임의 최외곽 폭으로 사용한다. 좌우 투명 영역은 새 프레임으로&lt;br /&gt;
채우지 않는다. 형상은 위가 넓고 아래가 좁아지도록 뒤집고, 기존 상단&lt;br /&gt;
프레임 하단 9px 안으로 겹쳐 하나로 접합한다.&lt;br /&gt;
좌우 경사는 기존 설정·기업 버튼 사선의 화면상 기울기를 그대로 연장한다.&lt;br /&gt;
외곽·프레임 면·우물·버튼의 x 좌표도 같은 직선식으로 투영해 전 층의&lt;br /&gt;
사선이 서로 평행하고 끊김 없이 이어지게 한다.&lt;br /&gt;
두 프레임이 실제로 포개지는 상단 8px에서는 #050505를 #1d1d1d 면으로&lt;br /&gt;
덮는다. 좌우 방향선은 외곽선에서 재계산하지 않고 기존 설정 버튼의 좌측&lt;br /&gt;
인셋선과 기업 버튼의 우측 인셋선을 직접 읽어 같은 직선으로 연장한다.&lt;br /&gt;
&lt;br /&gt;
초기 길라잡이 규격:&lt;br /&gt;
- 버튼 외곽 22px&lt;br /&gt;
- 프레임 실색 #1d1d1d 7px&lt;br /&gt;
- 우물 간격 2px&lt;br /&gt;
- 전체 부착 행 42px&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 42px !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    min-height:42px !important;&lt;br /&gt;
    max-height:42px !important;&lt;br /&gt;
    margin:-9px 0 0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    z-index:4 !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-nav,&lt;br /&gt;
.main-portal .portal-section-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:42px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-edge,&lt;br /&gt;
.main-portal .portal-section-frame-surface,&lt;br /&gt;
.main-portal .portal-section-frame-well,&lt;br /&gt;
.main-portal .portal-section-join-side-cover,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-edge,&lt;br /&gt;
.main-portal .portal-section-button-geometry .portal-cat-surface {&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-join-side-cover {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;portal-section-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:7px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    z-index:2147483647;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-highlight,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-highlight,&lt;br /&gt;
.main-portal .portal-section-well-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-frame-shadow,&lt;br /&gt;
.main-portal .portal-section-well-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-anchor,&lt;br /&gt;
.main-portal .portal-section-button-geometry {&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-section-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 선언문 전용 장식은 길라잡이 화면으로 넘어갈 때 함께 숨긴다. */&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal.is-guide-screen-active .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    visibility:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-manifesto {&lt;br /&gt;
        padding-top:10px;&lt;br /&gt;
        overflow-x:auto;&lt;br /&gt;
        justify-content:flex-start;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 버튼&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단의 설정–프로젝트–기업 및 공동체 합산 폭을 공유한다.&lt;br /&gt;
사선 방향은 상단 묶음과 반대로, 넓은 변이 하단에 놓인다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 auto;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    background:var(--well, #080808);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-svg {&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:65px;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-label {&lt;br /&gt;
    fill:#e2e2e2;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 본문 데코가 별도 stacking context를 만들더라도 길라잡이 클릭면을 최상단에 유지한다. */&lt;br /&gt;
.main-portal .portal-guide-nav,&lt;br /&gt;
.main-portal .portal-guide-svg,&lt;br /&gt;
.main-portal .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 본문 패널 / 실제 래퍼·데코 클리핑&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
구조:&lt;br /&gt;
로고 → 상단 버튼 → #1d1d1d 본문 프레임&lt;br /&gt;
                    └ #080808 우물&lt;br /&gt;
                       ├ 선언문&lt;br /&gt;
                       ├ 대문 데코&lt;br /&gt;
                       └ 길라잡이&lt;br /&gt;
&lt;br /&gt;
외곽 프레임과 내부 우물 모두 paint를 잘라낸다.&lt;br /&gt;
따라서 데코는 저장된 좌표와 z-index에 관계없이&lt;br /&gt;
#1d1d1d 프레임의 경계를 넘어 보일 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:2px 0 0;&lt;br /&gt;
    padding:9px;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#1d1d1d;&lt;br /&gt;
    border:1px solid #050505;&lt;br /&gt;
    border-radius:0;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    position:relative;&lt;br /&gt;
    display:flex;&lt;br /&gt;
    flex-direction:column;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:#080808;&lt;br /&gt;
    border:0;&lt;br /&gt;
    /* 오목한 우물의 안쪽 경계: 상단/우측 어둡게, 좌측/하단 밝게. */&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 선언문 좌표와 폭은 유지하되 새 우물 안의 흐름 요소로 둔다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 0 auto;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    /*&lt;br /&gt;
    길라잡이 하단에 추가한 3px을 전체 높이에 더하지 않는다.&lt;br /&gt;
    기존 하단 여유 24px 안에서 3px을 넘겨 전체 패널 높이를 보존한다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:10px 0 21px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    top:auto !important;&lt;br /&gt;
    left:auto !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    transform:none !important;&lt;br /&gt;
    z-index:1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
대문 장식 렌더러가 일반 장식을 이 우물의 직접 자식으로 넣는다.&lt;br /&gt;
position:absolute를 보장하고, 우물 overflow:hidden으로 절단한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-body-well &amp;gt; .wiki-decoration,&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-wiki-decoration-rendered=&amp;quot;1&amp;quot;] {&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 우물 최하단의 정상 흐름 요소다. */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    min-width:0 !important;&lt;br /&gt;
    max-width:none !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    /*&lt;br /&gt;
    위쪽은 2px.&lt;br /&gt;
    아래쪽은 우물의 1px inset을 제외하고 순수 #080808이 2px 보이도록&lt;br /&gt;
    CSS margin-bottom을 3px로 둔다.&lt;br /&gt;
    */&lt;br /&gt;
    margin:2px 0 3px !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-well .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-svg,&lt;br /&gt;
.main-portal .main-body-well .portal-guide-anchor {&lt;br /&gt;
    position:relative;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실패했던 display:contents 우회 구조는 사용하지 않는다. */&lt;br /&gt;
.main-portal &amp;gt; .main-body-panel,&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 높이 체인 / 외부 스크롤 완전 제거&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
Layout.css의 .mw-parser-output은 일반 문서를 위해 overflow-y:auto이다.&lt;br /&gt;
대문에서는 전체 패널이 남은 높이를 정확히 나눠 써야 하므로 예외 처리한다.&lt;br /&gt;
&lt;br /&gt;
핵심:&lt;br /&gt;
- 바깥 스크롤 박스 비활성화&lt;br /&gt;
- 높이 전달 체인을 100%로 고정&lt;br /&gt;
- 각 세로 flex 구간을 flex:1 1 0으로 축소 가능하게 변경&lt;br /&gt;
- min-height:0으로 min-content 높이 강제 확장을 차단&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main &amp;gt; .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main #mw-content-text,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-body-content,&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    display:flex !important;&lt;br /&gt;
    flex-direction:column !important;&lt;br /&gt;
    overflow-y:hidden !important;&lt;br /&gt;
    overflow-x:hidden !important;&lt;br /&gt;
    scrollbar-width:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output::-webkit-scrollbar {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .liberty-content-main .mw-parser-output &amp;gt; .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:100% !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:100% !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-well &amp;gt; .main-manifesto {&lt;br /&gt;
    flex:1 1 0 !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 길라잡이는 42px 본체·광원 + 상하 1px #050505 외곽선으로 44px이다.&lt;br /&gt;
   상하 2px #080808 간격, 상단 9px·하단 8px 프레임을 포함한 전체 행은 65px이다.&lt;br /&gt;
   상단 9px 중 외곽선·광원선을 제외한 #1d1d1d 실색 영역은 7px이다. */&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:0 0 65px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
버튼 프레임 / 상단 유지·하단 연속 용접&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단 총 36px:&lt;br /&gt;
4px 프레임 + 2px #080808 + 24px 버튼 + 2px #080808 + 4px 프레임&lt;br /&gt;
&lt;br /&gt;
하단 총 65px:&lt;br /&gt;
중앙에서 위로 솟은 패널 하단 프레임 + 2px #080808&lt;br /&gt;
+ 44px 길라잡이 외곽 + 2px #080808 + 공유 하단 프레임&lt;br /&gt;
&lt;br /&gt;
하단은 별도 ㄷ자 상자가 아니다.&lt;br /&gt;
본문 패널의 하단 외곽선 자체가 중앙에서 사선으로 올라갔다 내려오는&lt;br /&gt;
하나의 연속된 프레임이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단: 버튼 24px / 프레임과 2px 간격을 실제로 확보&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:none !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:2px !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    /* 2px 간격은 순수 #080808이며 별도 광원선을 두지 않는다. */&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:24px !important;&lt;br /&gt;
    min-height:24px !important;&lt;br /&gt;
    max-height:24px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
하단: 기본 하단 프레임 위로만 중앙 버튼 프레임이 돌출&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
본문 패널의 기본 하단 프레임 두께는 유지하고,&lt;br /&gt;
그 위로만 길라잡이 사다리꼴 프레임이 돌출된다.&lt;br /&gt;
패널 전체가 두꺼운 하단 밴드가 되지 않도록 한다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    left:0 !important;&lt;br /&gt;
    right:0 !important;&lt;br /&gt;
    bottom:0 !important;&lt;br /&gt;
    width:auto !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    isolation:isolate !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
    pointer-events:auto !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    z-index:2147483000 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기본 하단 프레임 + 중앙 돌출 프레임 */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-weld-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-weld-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 하단 기본 프레임: 상단만 #555555, 하단만 #101010. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 실제 길라잡이 버튼 외곽은 44px 사다리꼴이다. 바깥 돌출 프레임만 하단 기본 프레임과 용접된다. */&lt;br /&gt;
.main-portal .portal-guide-nav.is-continuous-weld&lt;br /&gt;
.portal-cat-inset-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
길라잡이 진입 상태:&lt;br /&gt;
- 전체 좌표계가 65px에서 45px로 실제 축소된다.&lt;br /&gt;
- 중앙 버튼은 44px에서 24px으로만 줄고, #1d1d1d 실면 7px의 9/8px 프레임은 유지한다.&lt;br /&gt;
- 좌우 우물은 중앙 버튼과 접합되는 단일 메인 프레임이 하단에서 올라와 메운다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .portal-guide-button-geometry {&lt;br /&gt;
    transform:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame {&lt;br /&gt;
    transform-box:fill-box;&lt;br /&gt;
    transform-origin:center bottom;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-edge {&lt;br /&gt;
    fill:#050505;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-surface {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-top,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-highlight-left {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-right,&lt;br /&gt;
.main-portal .portal-guide-rising-main-frame-shadow-bottom {&lt;br /&gt;
    fill:#101010;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 예전 우물 내부 mount 규칙 차단 */&lt;br /&gt;
.main-portal .main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;],&lt;br /&gt;
body.action-view.clbi-main-page&lt;br /&gt;
.main-portal&lt;br /&gt;
.main-body-well &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    flex:none !important;&lt;br /&gt;
    width:0 !important;&lt;br /&gt;
    height:0 !important;&lt;br /&gt;
    min-height:0 !important;&lt;br /&gt;
    max-height:0 !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:0 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media screen and (max-width:700px) {&lt;br /&gt;
    .main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;],&lt;br /&gt;
    .main-portal .main-body-panel {&lt;br /&gt;
        min-width:660px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
광원선 중복 방지 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
#555555는 독립된 #1d1d1d 돌출면의 상단·우측에만 사용한다.&lt;br /&gt;
#101010은 같은 면의 좌측·하단에만 사용한다.&lt;br /&gt;
#080808 우물과 2px 간격에는 광원선을 추가하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물은 오목한 방향성 선을 유지한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
프레임 안쪽선·하단 아치 오버레이 확정 규칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
상단:&lt;br /&gt;
- 기존 구조와 치수 유지&lt;br /&gt;
- #1d1d1d 프레임의 안쪽 경계에 방향성 1px 선 추가&lt;br /&gt;
- 안쪽 우물 경계는 상단/우측 #101010, 좌측/하단 #555555&lt;br /&gt;
- 선은 프레임의 마지막 1px에 놓여 2px #080808 간격을 침범하지 않음&lt;br /&gt;
&lt;br /&gt;
하단:&lt;br /&gt;
- main-body-panel의 원래 전체 프레임을 그대로 복구&lt;br /&gt;
- SVG는 전체 하단 프레임을 다시 그리지 않음&lt;br /&gt;
- 버튼과 하부를 비운 중앙 아치형 프레임 조각만 기존 프레임 위에 얹음&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 상단 외곽 프레임의 안쪽 우물 경계&lt;br /&gt;
   우물은 오목하므로 돌출면과 반대 방향이다.&lt;br /&gt;
   상단/우측 #101010, 좌측/하단 #555555. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:&amp;quot;&amp;quot;;&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:8px;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    border-style:solid;&lt;br /&gt;
    border-width:1px;&lt;br /&gt;
    border-color:#101010 #101010 #555555 #555555;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 2px 우물 간격과 버튼은 안쪽선보다 아래 레이어에 둔다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    z-index:1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 기존 본문 패널의 전체 프레임을 복구한다. */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:9px !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 오버레이 좌표계. 패널 하부 자체는 투명하게 둔다. */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] &amp;gt; .portal-guide-nav,&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] .portal-guide-svg {&lt;br /&gt;
    height:65px !important;&lt;br /&gt;
    min-height:65px !important;&lt;br /&gt;
    max-height:65px !important;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임 면과 내부 우물 */&lt;br /&gt;
.main-portal .portal-guide-arch-outer {&lt;br /&gt;
    fill:#1d1d1d;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-well {&lt;br /&gt;
    fill:#080808;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치의 최외곽: 상단과 양쪽 사선만 */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    stroke:#050505;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 바깥 돌출면의 광원 */&lt;br /&gt;
.main-portal .portal-guide-arch-highlight {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-shadow {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* 버튼과 마주 보는 하단 본문 프레임의 상단 1px만 담당한다.&lt;br /&gt;
   선 stroke가 아니라 정수 좌표의 1px rect이므로 2px로 번지지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-facing-highlight {&lt;br /&gt;
    fill:#555555;&lt;br /&gt;
    stroke:none;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 안쪽 우물 경계: 상단/우측 어두움, 좌측 밝음. 하부는 열림. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-highlight {&lt;br /&gt;
    stroke:#101010;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-arch-inner-shadow {&lt;br /&gt;
    stroke:#555555;&lt;br /&gt;
    stroke-width:1;&lt;br /&gt;
    fill:none;&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 SVG가 그리던 독립 하단 프레임은 강제로 숨긴다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-surface,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-edge,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-highlight,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-shadow,&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner,&lt;br /&gt;
.main-portal .portal-guide-weld-outer,&lt;br /&gt;
.main-portal .portal-guide-weld-well,&lt;br /&gt;
.main-portal .portal-guide-weld-edge,&lt;br /&gt;
.main-portal .portal-guide-weld-highlight,&lt;br /&gt;
.main-portal .portal-guide-weld-shadow {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
최종 시각 픽셀 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
목표는 선언값이 아니라 화면에서 보이는 색 면이다.&lt;br /&gt;
&lt;br /&gt;
상단 카테고리:&lt;br /&gt;
- 우물 inset 1px&lt;br /&gt;
- 순수 #080808 간격 2px&lt;br /&gt;
- 실제 버튼 22px&lt;br /&gt;
- 기존 전체 행 높이는 유지&lt;br /&gt;
&lt;br /&gt;
본문 패널:&lt;br /&gt;
- #050505 외곽선 1px&lt;br /&gt;
- 방향성 inset 1px&lt;br /&gt;
- 순수 #1d1d1d 면 7px&lt;br /&gt;
- 내부 #080808 우물&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
상단 카테고리: 실제 보이는 우물 간격 2px&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #555555,&lt;br /&gt;
        inset -1px 0 0 #555555,&lt;br /&gt;
        inset 1px 0 0 #101010,&lt;br /&gt;
        inset 0 -1px 0 #101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전 border 기반 내부선은 코너 혼색을 만들므로 완전히 제거한다. */&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;]::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
우물 inset 1px + padding 3px.&lt;br /&gt;
첫 1px은 inset이고, 그 뒤 순수 #080808이 정확히 2px 남는다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] &amp;gt; .portal-category-nav {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:auto !important;&lt;br /&gt;
    margin:0 !important;&lt;br /&gt;
    padding:3px !important;&lt;br /&gt;
    box-sizing:border-box !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal &amp;gt; [data-component=&amp;quot;category-nav&amp;quot;] .portal-category-svg {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    inset:auto !important;&lt;br /&gt;
    width:100% !important;&lt;br /&gt;
    height:22px !important;&lt;br /&gt;
    min-height:22px !important;&lt;br /&gt;
    max-height:22px !important;&lt;br /&gt;
    overflow:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ─────────────────────────────────────────&lt;br /&gt;
본문 패널: 순수 #1d1d1d 면 7px + 선명한 inset&lt;br /&gt;
───────────────────────────────────────── */&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    background:#1d1d1d !important;&lt;br /&gt;
    border:1px solid #050505 !important;&lt;br /&gt;
    border-radius:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
box-shadow 코너 중첩 대신 네 개의 불투명 1px 면을 직접 그린다.&lt;br /&gt;
첫 번째 레이어가 코너 우선권을 가진다.&lt;br /&gt;
- 상단 #555555가 좌상단/우상단 우선&lt;br /&gt;
- 우측 #555555가 우하단 우선&lt;br /&gt;
- 좌측/하단 #101010&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel::before,&lt;br /&gt;
.main-portal .main-body-panel::before {&lt;br /&gt;
    content:&amp;quot;&amp;quot; !important;&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    position:absolute !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482990 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    background:&lt;br /&gt;
        linear-gradient(#555555,#555555) 0 0 / 100% 1px no-repeat,&lt;br /&gt;
        linear-gradient(#555555,#555555) 100% 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 0 / 1px 100% no-repeat,&lt;br /&gt;
        linear-gradient(#101010,#101010) 0 100% / 100% 1px no-repeat !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 본문 우물의 오목한 inset도 최종 규칙으로 고정한다. */&lt;br /&gt;
.main-portal .main-body-well {&lt;br /&gt;
    background:#080808 !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:&lt;br /&gt;
        inset 0 1px 0 #101010,&lt;br /&gt;
        inset -1px 0 0 #101010,&lt;br /&gt;
        inset 1px 0 0 #555555,&lt;br /&gt;
        inset 0 -1px 0 #555555 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
본문 프레임 최종 시각 폭 7px&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
inset은 layout 폭을 소비하지 않고 padding 면 위에 그려진다.&lt;br /&gt;
따라서 순수 프레임 폭 7px 목표에는 padding 자체를 7px로 둔다.&lt;br /&gt;
상단 버튼과 하단 아치 구조는 변경하지 않는다.&lt;br /&gt;
*/&lt;br /&gt;
body.action-view.clbi-main-page .main-portal .main-body-panel,&lt;br /&gt;
.main-portal .main-body-panel {&lt;br /&gt;
    /*&lt;br /&gt;
    inset 1px이 프레임 면 위를 덮으므로 레이아웃 padding은 8px을 사용한다.&lt;br /&gt;
    최종 화면에서 보이는 순수 #1d1d1d 면은 7px이다.&lt;br /&gt;
    */&lt;br /&gt;
    padding:8px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 아치 프레임 외곽·하단 안쪽선 정확 보정 / 20260712&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
대상 구분:&lt;br /&gt;
- 버튼 자체의 외곽 #050505는 유지&lt;br /&gt;
- 아치형 바깥 프레임의 외부 #050505만 제거&lt;br /&gt;
- 버튼 하단 inset은 기본 #101010 유지&lt;br /&gt;
- 아치 프레임 하단 안쪽 면에만 #555555 1px 추가&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/* 버튼 자체는 원래 마감으로 복구한다. */&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-edge {&lt;br /&gt;
    fill:#050505 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .portal-guide-nav .portal-cat-inset-bottom {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치형 프레임 바깥의 #050505 외곽선만 제거한다. */&lt;br /&gt;
.main-portal .portal-guide-arch-edge {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 아치 프레임의 실제 하단 안쪽 면. 버튼과는 별도 요소다. */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:block !important;&lt;br /&gt;
    fill:#555555 !important;&lt;br /&gt;
    stroke:none !important;&lt;br /&gt;
    shape-rendering:crispEdges;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* 이전에 잘못 집었던 패널 하단 보조선에는 밝은 선을 두지 않는다. */&lt;br /&gt;
.main-portal .portal-guide-panel-bottom-inner {&lt;br /&gt;
    stroke:#101010 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 길라잡이 통합 프레임 위치 확정 / 20260713&lt;br /&gt;
본문 패널의 원래 하단 프레임은 분리하지 않는다.&lt;br /&gt;
버튼·아치·클릭 영역만 현재 상태에서 1px 위로 이동한다.&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .main-body-panel &amp;gt; [data-component=&amp;quot;category-guide-nav&amp;quot;] {&lt;br /&gt;
    bottom:1px !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
하단 중앙 접합: 애드온 프레임 1px 연장 / 20260713&lt;br /&gt;
- 본문 패널의 원래 전체 프레임은 그대로 유지&lt;br /&gt;
- 버튼과 좌·우·상단 아치 프레임만 애드온&lt;br /&gt;
- 별도 #555555 하단선은 생성하지 않음&lt;br /&gt;
- JS가 아치 외곽면만 아래로 1px 연장하여 원래 우물 하단선을 같은 행에서 덮음&lt;br /&gt;
----------------------------------------- */&lt;br /&gt;
.main-portal .portal-guide-arch-inner-bottom {&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* -----------------------------------------&lt;br /&gt;
대문 선언문 절차적 진입 애니메이션 / 20260713&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
제목·구분선·본문 행은 실제 HTML 요소를 Web Animations API로 움직인다.&lt;br /&gt;
CSS transition 클래스 전환에 의존하지 않아 초기 진입·새로고침·SPA에서&lt;br /&gt;
최종 상태로 순간 점프하는 문제를 피한다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
원문은 기본 표시 상태다.&lt;br /&gt;
JS가 폰트·행 측정·구분선 DOM까지 모두 준비한 뒤 붙이는&lt;br /&gt;
.manifesto-intro-prepared만 애니메이션의 초기 프레임을 소유한다.&lt;br /&gt;
따라서 강력 새로고침이나 raw 스크립트 실패가 본문을 숨길 수 없다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title {&lt;br /&gt;
    position:relative !important;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title::before,&lt;br /&gt;
.main-portal .main-manifesto-title::after {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    right:0;&lt;br /&gt;
    bottom:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    background:transparent !important;&lt;br /&gt;
    border:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:4;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ManifestoIntro가 소유권을 얻은 뒤에는 이전 구분선 소스를 모두 차단한다. */&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title {&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
    box-shadow:none !important;&lt;br /&gt;
    background-image:none !important;&lt;br /&gt;
    text-decoration:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::before,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-title::after,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-inner &amp;gt; hr,&lt;br /&gt;
.main-portal.manifesto-divider-controlled .main-manifesto-divider,&lt;br /&gt;
.main-portal.manifesto-divider-controlled [data-manifesto-divider-track] {&lt;br /&gt;
    content:none !important;&lt;br /&gt;
    display:none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    top:0;&lt;br /&gt;
    width:50%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    transform:scaleX(0);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-left {&lt;br /&gt;
    left:0;&lt;br /&gt;
    transform-origin:100% 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-right {&lt;br /&gt;
    right:0;&lt;br /&gt;
    transform-origin:0 50%;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
제목 구분선이 완성된 뒤 같은 위치에서 분리되어 본문 하단까지 내려가는 실선.&lt;br /&gt;
최종 이동 거리는 ManifestoIntro가 실제 렌더된 본문 높이를 정수 px로 측정한다.&lt;br /&gt;
*/&lt;br /&gt;
.main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:1px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    background:#626262;&lt;br /&gt;
    border:0;&lt;br /&gt;
    box-shadow:none;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    z-index:3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy.manifesto-line-measuring .manifesto-measure-token {&lt;br /&gt;
    display:inline;&lt;br /&gt;
    white-space:normal;&lt;br /&gt;
    font:inherit;&lt;br /&gt;
    color:inherit;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    min-height:0;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
    will-change:height, max-height, opacity, clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:26px;&lt;br /&gt;
    margin:0;&lt;br /&gt;
    padding:0;&lt;br /&gt;
    line-height:26px !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
    will-change:transform, opacity;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy {&lt;br /&gt;
    visibility:visible !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:0;&lt;br /&gt;
    max-height:0;&lt;br /&gt;
    overflow:hidden;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    clip-path:inset(0 0 100% 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:.18;&lt;br /&gt;
    transform:translateY(-10px);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    border-bottom:0 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
    transform:scaleX(1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line {&lt;br /&gt;
    height:26px;&lt;br /&gt;
    max-height:26px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    clip-path:inset(0 0 0 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.main-portal.manifesto-intro-complete .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transform:translateY(0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-title {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        border-bottom:0 !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider {&lt;br /&gt;
        display:block !important;&lt;br /&gt;
        left:0 !important;&lt;br /&gt;
        width:100% !important;&lt;br /&gt;
        background:transparent !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-title &amp;gt; .manifesto-divider &amp;gt; .manifesto-divider-half {&lt;br /&gt;
        transform:scaleX(1) !important;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal .main-manifesto-inner &amp;gt; .manifesto-divider-drop {&lt;br /&gt;
        opacity:1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line,&lt;br /&gt;
    .main-portal.manifesto-intro-prepared .main-manifesto-copy .manifesto-line-inner {&lt;br /&gt;
        height:26px;&lt;br /&gt;
        max-height:26px;&lt;br /&gt;
        overflow:visible;&lt;br /&gt;
        opacity:1;&lt;br /&gt;
        clip-path:none;&lt;br /&gt;
        transform:none;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4646</id>
		<title>미디어위키:Common.js</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.js&amp;diff=4646"/>
		<updated>2026-07-17T00:25:09Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-css-top-cover-20260717 / Common.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;mw.loader.load(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* Raw MediaWiki script cache key.&lt;br /&gt;
   This is not a semantic version. It only prevents stale raw JS during active editing. */&lt;br /&gt;
window.CLBI_RAW_LOAD_BUST = &#039;portal-section-top-face-seven-20260717-038&#039;;&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Raw script loads are tracked so the site boot gate can wait for real subsystem scripts&lt;br /&gt;
before releasing the normal surface.  The old prefixed load-bust variable remains for&lt;br /&gt;
compatibility only; new loader state uses unprefixed EntryScriptLoads/EntryRawScriptPromises.&lt;br /&gt;
*/&lt;br /&gt;
window.EntryScriptLoads = window.EntryScriptLoads || [];&lt;br /&gt;
window.EntryRawScriptPromises = window.EntryRawScriptPromises || {};&lt;br /&gt;
function buildEntryRawScriptUrl(key) {&lt;br /&gt;
    var url = &#039;/index.php?title=&#039; + encodeURIComponent(key) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;;&lt;br /&gt;
    var token;&lt;br /&gt;
    var sep;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, key);&lt;br /&gt;
        }&lt;br /&gt;
        if (/[?&amp;amp;]_entryRev=/.test(url)) return url;&lt;br /&gt;
        token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(&#039;raw-script-fallback&#039;) : window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        token = window.CLBI_RAW_LOAD_BUST;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
    return url + sep + &#039;v=&#039; + encodeURIComponent(String(token || &#039;entry-raw&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadClbiRawScript(title) {&lt;br /&gt;
    var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
    var promise;&lt;br /&gt;
&lt;br /&gt;
    if (!key) return Promise.resolve(false);&lt;br /&gt;
    if (window.EntryRawScriptPromises[key]) return window.EntryRawScriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
    promise = Promise.resolve()&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.ensureLoaded === &#039;function&#039;) {&lt;br /&gt;
                return window.RevisionManifest.ensureLoaded();&lt;br /&gt;
            }&lt;br /&gt;
            return null;&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function () { return null; })&lt;br /&gt;
        .then(function () {&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
                script.src = buildEntryRawScriptUrl(key);&lt;br /&gt;
                script.async = true;&lt;br /&gt;
                script.setAttribute(&#039;data-entry-raw-script&#039;, key);&lt;br /&gt;
                script.onload = function () { resolve({ title: key, ok: true, src: script.src }); };&lt;br /&gt;
                script.onerror = function () { resolve({ title: key, ok: false, src: script.src }); };&lt;br /&gt;
                (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    window.EntryRawScriptPromises[key] = promise;&lt;br /&gt;
    window.EntryScriptLoads.push(promise);&lt;br /&gt;
    return promise;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Main-page fresh assets with fallback&lt;br /&gt;
========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css의 MainPage.css import는 안전망으로 그대로 유지한다.&lt;br /&gt;
대문에서는 새로고침마다 timestamp가 붙은 CSS/JS를 추가로 불러와&lt;br /&gt;
브라우저·프록시의 오래된 raw 응답을 우회한다.&lt;br /&gt;
&lt;br /&gt;
이 보조 로더가 실패해도 기존 정적 import와 기존 화면은 남는다.&lt;br /&gt;
*/&lt;br /&gt;
window.MainPageFreshAssets = window.MainPageFreshAssets || (function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var sessionToken = String(Date.now());&lt;br /&gt;
    var ASSET_LOAD_TIMEOUT_MS = 5000;&lt;br /&gt;
    var scriptPromises = {};&lt;br /&gt;
    var styleNode = null;&lt;br /&gt;
&lt;br /&gt;
    function isMainPage() {&lt;br /&gt;
        var page = &#039;&#039;;&lt;br /&gt;
        try {&lt;br /&gt;
            page = String(mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;);&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return page.replace(/_/g, &#039; &#039;) === &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrl(title, type) {&lt;br /&gt;
        var ctype = type === &#039;style&#039; ? &#039;text/css&#039; : &#039;text/javascript&#039;;&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) +&lt;br /&gt;
            &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype) +&lt;br /&gt;
            &#039;&amp;amp;_mainPageFresh=&#039; + encodeURIComponent(sessionToken);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadStyle() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        if (styleNode &amp;amp;&amp;amp; styleNode.parentNode) {&lt;br /&gt;
            return Promise.resolve(true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var link = document.createElement(&#039;link&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            link.rel = &#039;stylesheet&#039;;&lt;br /&gt;
            link.href = rawUrl(&#039;MediaWiki:MainPage.css&#039;, &#039;style&#039;);&lt;br /&gt;
            link.setAttribute(&#039;data-main-page-fresh-style&#039;, sessionToken);&lt;br /&gt;
            link.onload = function () {&lt;br /&gt;
                styleNode = link;&lt;br /&gt;
                settle(true);&lt;br /&gt;
            };&lt;br /&gt;
            link.onerror = function () {&lt;br /&gt;
                /* Common.css의 정적 import가 그대로 남아 있으므로 화면은 유지된다. */&lt;br /&gt;
                settle(false);&lt;br /&gt;
            };&lt;br /&gt;
            timeout = window.setTimeout(function () { settle(false); }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(link);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadScript(title) {&lt;br /&gt;
        var key = String(title || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!key || !isMainPage()) return Promise.resolve(false);&lt;br /&gt;
        if (scriptPromises[key]) return scriptPromises[key];&lt;br /&gt;
&lt;br /&gt;
        scriptPromises[key] = new Promise(function (resolve) {&lt;br /&gt;
            var script = document.createElement(&#039;script&#039;);&lt;br /&gt;
            var settled = false;&lt;br /&gt;
            var timeout = 0;&lt;br /&gt;
&lt;br /&gt;
            function settle(ok) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                window.clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            script.src = rawUrl(key, &#039;script&#039;);&lt;br /&gt;
            script.async = false;&lt;br /&gt;
            script.setAttribute(&#039;data-main-page-fresh-script&#039;, key);&lt;br /&gt;
            script.onload = function () { settle(true); };&lt;br /&gt;
            script.onerror = function () { settle(false); };&lt;br /&gt;
            timeout = window.setTimeout(function () {&lt;br /&gt;
                if (script.parentNode) script.parentNode.removeChild(script);&lt;br /&gt;
                settle(false);&lt;br /&gt;
            }, ASSET_LOAD_TIMEOUT_MS);&lt;br /&gt;
            (document.head || document.documentElement).appendChild(script);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return scriptPromises[key];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensure() {&lt;br /&gt;
        if (!isMainPage()) return Promise.resolve(false);&lt;br /&gt;
&lt;br /&gt;
        return loadStyle()&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:CategoryPillar.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:PortalSectionNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                return loadScript(&#039;MediaWiki:BottomGuideNav.js&#039;);&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (window.CategoryNav &amp;amp;&amp;amp; typeof window.CategoryNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.CategoryPillar &amp;amp;&amp;amp; typeof window.CategoryPillar.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.CategoryPillar.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.PortalSectionNav &amp;amp;&amp;amp; typeof window.PortalSectionNav.renderAll === &#039;function&#039;) {&lt;br /&gt;
                    window.PortalSectionNav.renderAll(document);&lt;br /&gt;
                }&lt;br /&gt;
                if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.render === &#039;function&#039;) {&lt;br /&gt;
                    window.BottomGuideNav.render();&lt;br /&gt;
                }&lt;br /&gt;
                return true;&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function status() {&lt;br /&gt;
        return {&lt;br /&gt;
            mainPage: isMainPage(),&lt;br /&gt;
            token: sessionToken,&lt;br /&gt;
            style: !!document.querySelector(&#039;link[data-main-page-fresh-style]&#039;),&lt;br /&gt;
            category: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            pillar: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:CategoryPillar.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            section: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:PortalSectionNav.js&amp;quot;]&#039;&lt;br /&gt;
            ),&lt;br /&gt;
            guide: !!document.querySelector(&lt;br /&gt;
                &#039;script[data-main-page-fresh-script=&amp;quot;MediaWiki:BottomGuideNav.js&amp;quot;]&#039;&lt;br /&gt;
            )&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            ensure();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(ensure, 0);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        ensure: ensure,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}(window, document, window.mw));&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Account access page marker&lt;br /&gt;
   =========================================&lt;br /&gt;
   New shared systems use unprefixed names.  The server body class differs&lt;br /&gt;
   between skins/locales, so the canonical special-page name is normalized&lt;br /&gt;
   once and exposed as a stable page-state class for the login surface.&lt;br /&gt;
*/&lt;br /&gt;
(function markAccountLoginPage(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var canonical = &#039;&#039;;&lt;br /&gt;
    var pageName = &#039;&#039;;&lt;br /&gt;
    var isLoginPage = false;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        canonical = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039; : &#039;&#039;).toLowerCase();&lt;br /&gt;
        pageName = String(window.mw &amp;amp;&amp;amp; mw.config ? mw.config.get(&#039;wgPageName&#039;) || &#039;&#039; : &#039;&#039;).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    isLoginPage = canonical === &#039;userlogin&#039; || /^(?:special|특수):(?:userlogin|로그인)$/.test(pageName);&lt;br /&gt;
    if (!isLoginPage) return;&lt;br /&gt;
&lt;br /&gt;
    document.documentElement.classList.add(&#039;account-login-page-root&#039;);&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountTarget(link) {&lt;br /&gt;
        var href = &#039;&#039;;&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var text = &#039;&#039;;&lt;br /&gt;
        var sample = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!link) return false;&lt;br /&gt;
        href = String(link.getAttribute(&#039;href&#039;) || &#039;&#039;);&lt;br /&gt;
        title = String(link.getAttribute(&#039;title&#039;) || &#039;&#039;);&lt;br /&gt;
        text = String(link.textContent || &#039;&#039;);&lt;br /&gt;
        try { href = decodeURIComponent(href); } catch (err) {}&lt;br /&gt;
        sample = (href + &#039; &#039; + title + &#039; &#039; + text).replace(/_/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        return /(?:special|특수)\s*[:%]\s*(?:createaccount|계정\s*(?:만들기|생성))|createaccount|계정\s*(?:만들기|생성)/i.test(sample);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function suppressCreateAccountSurface(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var fixedSelectors = [&lt;br /&gt;
            &#039;.mw-createacct-benefits-container&#039;,&lt;br /&gt;
            &#039;.mw-createacct-benefits-list&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-cta&#039;,&lt;br /&gt;
            &#039;.mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;#mw-createaccount-join&#039;,&lt;br /&gt;
            &#039;.mw-userlogin-create&#039;&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        fixedSelectors.forEach(function (selector) {&lt;br /&gt;
            Array.prototype.forEach.call(scope.querySelectorAll(selector), function (node) {&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                node.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;a[href]&#039;), function (link) {&lt;br /&gt;
            var container;&lt;br /&gt;
            if (!isCreateAccountTarget(link)) return;&lt;br /&gt;
&lt;br /&gt;
            link.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            link.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            link.setAttribute(&#039;tabindex&#039;, &#039;-1&#039;);&lt;br /&gt;
&lt;br /&gt;
            container = link.closest(&#039;.mw-ui-vform-field, .oo-ui-fieldLayout, .mw-userlogin-create, p, li&#039;);&lt;br /&gt;
            if (container &amp;amp;&amp;amp; container.querySelectorAll(&#039;a&#039;).length === 1) {&lt;br /&gt;
                container.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
                container.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyMarker() {&lt;br /&gt;
        if (!document.body) return;&lt;br /&gt;
        document.body.classList.add(&#039;account-login-page&#039;);&lt;br /&gt;
        suppressCreateAccountSurface(document);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, applyMarker, { once: true });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyMarker();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                suppressCreateAccountSurface(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Boot gate prelude&lt;br /&gt;
   =========================================&lt;br /&gt;
   This tiny prelude runs before the full EntryStore/EntryLoader/BootGate implementation.&lt;br /&gt;
   Its job is only to guarantee that the user sees the separate loading surface before the&lt;br /&gt;
   normal wiki shell can paint.  The full BootGate later adopts the same DOM node and keeps&lt;br /&gt;
   it open until the real full-entry readiness contract is satisfied.&lt;br /&gt;
&lt;br /&gt;
   Maintenance rule:&lt;br /&gt;
   - Do not move first-screen hiding into a late subsystem callback.  If the boot gate is&lt;br /&gt;
     meant to protect the first impression, the surface must be activated before sidebars,&lt;br /&gt;
     navbars, document wells, nations panels, or other entry surfaces can visibly fill in.&lt;br /&gt;
   - New names remain unprefixed.  Existing prefixed names elsewhere are legacy aliases only.&lt;br /&gt;
*/&lt;br /&gt;
(function installBootGatePrelude(window, document) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SCREEN_ID = &#039;boot-gate-screen&#039;;&lt;br /&gt;
    var STYLE_ID = &#039;boot-gate-prelude-style&#039;;&lt;br /&gt;
    var START_TIME = Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getMwConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.mw &amp;amp;&amp;amp; window.mw.config &amp;amp;&amp;amp; typeof window.mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = window.mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDeveloperOrEditingPage() {&lt;br /&gt;
        var ns = Number(getMwConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(getMwConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(getMwConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Developer/system pages must stay practical.  Editing MediaWiki:, File:,&lt;br /&gt;
         * Project:, Template:, Module:, Category:, or any non-view action already forces&lt;br /&gt;
         * a full page load in MediaWiki, so showing the entry boot screen there only slows&lt;br /&gt;
         * maintenance work and does not improve the public first impression.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = getMwConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(getMwConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(getMwConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizePageName(getMwConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var anonymousUser = isAnonymousUser();&lt;br /&gt;
    var disabled = isAuthenticationPage() || (!anonymousUser &amp;amp;&amp;amp; (hasQueryFlag(&#039;bootGate&#039;, &#039;0&#039;) || isDeveloperOrEditingPage()));&lt;br /&gt;
&lt;br /&gt;
    function injectStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (disabled || document.getElementById(STYLE_ID)) return;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = STYLE_ID;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active,body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt;:not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen{position:fixed!important;inset:0!important;z-index:2147483000!important;display:flex!important;align-items:center!important;justify-content:center!important;background:#080808!important;color:#d8d8d8!important;opacity:1;pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-decoration-layer{position:absolute;inset:0;z-index:1;pointer-events:none;overflow:hidden;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-panel{position:relative;z-index:2;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen .boot-gate-close{display:none;position:absolute;right:7px;top:6px;z-index:3;height:18px;min-width:18px;border:1px solid #000;background:#141414;color:#ddd;font-size:11px;line-height:16px;padding:0 5px;cursor:pointer;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview{inset:auto!important;left:18px!important;top:18px!important;width:min(720px,calc(100vw - 380px))!important;height:360px!important;z-index:99990!important;overflow:hidden!important;border:1px solid #000!important;box-shadow:0 10px 28px rgba(0,0,0,.55)!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-panel,#boot-gate-screen.is-preview .boot-gate-close{pointer-events:auto!important;}&#039;,&lt;br /&gt;
            &#039;#boot-gate-screen.is-preview .boot-gate-close{display:block;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activate() {&lt;br /&gt;
        if (disabled) return;&lt;br /&gt;
        injectStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function makeNode() {&lt;br /&gt;
        var node;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var status;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var fill;&lt;br /&gt;
        var progress;&lt;br /&gt;
        var detail;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
&lt;br /&gt;
        if (disabled || !document.body) return null;&lt;br /&gt;
        activate();&lt;br /&gt;
&lt;br /&gt;
        node = document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) return node;&lt;br /&gt;
&lt;br /&gt;
        node = document.createElement(&#039;div&#039;);&lt;br /&gt;
        node.id = SCREEN_ID;&lt;br /&gt;
        node.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
        node.setAttribute(&#039;data-boot-gate-prelude&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        title.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        status = document.createElement(&#039;div&#039;);&lt;br /&gt;
        status.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        status.textContent = &#039;Preparing site entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        fill = document.createElement(&#039;div&#039;);&lt;br /&gt;
        fill.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(fill);&lt;br /&gt;
&lt;br /&gt;
        progress = document.createElement(&#039;div&#039;);&lt;br /&gt;
        progress.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        progress.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        detail = document.createElement(&#039;div&#039;);&lt;br /&gt;
        detail.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        detail.textContent = &#039;boot gate prelude&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { release(node); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(title);&lt;br /&gt;
        panel.appendChild(status);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(progress);&lt;br /&gt;
        panel.appendChild(detail);&lt;br /&gt;
        node.appendChild(decoLayer);&lt;br /&gt;
        node.appendChild(panel);&lt;br /&gt;
        node.appendChild(close);&lt;br /&gt;
&lt;br /&gt;
        document.body.insertBefore(node, document.body.firstChild || null);&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function onBody(callback) {&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            callback();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, callback, { once: true });&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function tick() {&lt;br /&gt;
            if (document.body) return callback();&lt;br /&gt;
            window.setTimeout(tick, 10);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function release(node) {&lt;br /&gt;
        node = node || document.getElementById(SCREEN_ID);&lt;br /&gt;
        if (node) {&lt;br /&gt;
            node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
            node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            }, 240);&lt;br /&gt;
        }&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (document.documentElement) document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    activate();&lt;br /&gt;
    onBody(function () {&lt;br /&gt;
        activate();&lt;br /&gt;
        makeNode();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.__BootGatePrelude = {&lt;br /&gt;
        startTime: START_TIME,&lt;br /&gt;
        activate: activate,&lt;br /&gt;
        ensure: makeNode,&lt;br /&gt;
        release: release,&lt;br /&gt;
        node: function () { return document.getElementById(SCREEN_ID); },&lt;br /&gt;
        disabled: disabled&lt;br /&gt;
    };&lt;br /&gt;
})(window, document);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Site boot gate and entry artifact contract&lt;br /&gt;
   =========================================&lt;br /&gt;
   This is the initial-load full/half entry artifact system.&lt;br /&gt;
&lt;br /&gt;
   Purpose:&lt;br /&gt;
   - The loading screen is not decorative. It exists only during the first site entry in a&lt;br /&gt;
     tab, before the user is allowed into the normal wiki surface.&lt;br /&gt;
   - A &amp;quot;full&amp;quot; entry must mean that the first view of that system can appear without an&lt;br /&gt;
     additional visible data load. For the nations system, the 1950 entry is full only when&lt;br /&gt;
     its nation list/link-map data and first-view pixel decorations are ready in this tab.&lt;br /&gt;
   - A &amp;quot;half&amp;quot; entry is a predictive warm state for the next likely path. It may fetch and&lt;br /&gt;
     parse data, but may skip expensive final work such as image/canvas preparation until it&lt;br /&gt;
     is promoted to full.&lt;br /&gt;
   - This principle is a site-wide design priority, like SPA continuity. New viewers,&lt;br /&gt;
     document systems, and information panels should define their entry full/half packs&lt;br /&gt;
     before exposing a first screen that can visibly fill in later.&lt;br /&gt;
   - SPA navigation is a consumer phase, not a blocking phase.  Do not add BootGate holds&lt;br /&gt;
     to SPA route changes.  If a route needs seamless first paint, prepare its artifacts&lt;br /&gt;
     during the initial boot pack and have the subsystem consume EntryStore synchronously&lt;br /&gt;
     before inserting or painting visible DOM.&lt;br /&gt;
&lt;br /&gt;
   Naming rule:&lt;br /&gt;
   - New public APIs, globals, classes, functions, files, and components must not use a&lt;br /&gt;
     project prefix. Old prefixed globals are legacy compatibility surfaces only.&lt;br /&gt;
   - New code should use names such as BootGate, EntryLoader, EntryStore, and boot-gate-*.&lt;br /&gt;
*/&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var MANIFEST_TITLE = &#039;MediaWiki:EntryManifest.json&#039;;&lt;br /&gt;
    var BUILD_ID = &#039;20260711-existing-account-login-001&#039;;&lt;br /&gt;
    var deferredEntryWarmups = [];&lt;br /&gt;
    var READY_KEY = &#039;boot-gate-ready-version&#039;;&lt;br /&gt;
    var DISMISS_PARAM = &#039;bootGate&#039;;&lt;br /&gt;
    var bootStarted = false;&lt;br /&gt;
    var bootPromise = null;&lt;br /&gt;
    var bootNode = null;&lt;br /&gt;
    var bootStatusNode = null;&lt;br /&gt;
    var bootProgressNode = null;&lt;br /&gt;
    var bootDetailNode = null;&lt;br /&gt;
    var bootFillNode = null;&lt;br /&gt;
    var bootStartTime = 0;&lt;br /&gt;
    var earlyBootStyleInjected = false;&lt;br /&gt;
    var loginGateLocked = false;&lt;br /&gt;
    var loginGateActionNode = null;&lt;br /&gt;
&lt;br /&gt;
    function hasQueryFlag(name, value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + name + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return !!(match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readConfig(name, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.config &amp;amp;&amp;amp; typeof mw.config.get === &#039;function&#039;) {&lt;br /&gt;
                var value = mw.config.get(name);&lt;br /&gt;
                return value == null ? fallback : value;&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBootPageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).split(&#039;?&#039;)[0].replace(/^\/index\.php\//, &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isCreateAccountPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        return canonical === &#039;createaccount&#039; || /^(?:special|특수):(?:createaccount|계정 ?(?:만들기|생성))/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnonymousUser() {&lt;br /&gt;
        var userName = readConfig(&#039;wgUserName&#039;, null);&lt;br /&gt;
        var userId = Number(readConfig(&#039;wgUserId&#039;, 0) || 0);&lt;br /&gt;
        return !userName &amp;amp;&amp;amp; !userId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAuthenticationPage() {&lt;br /&gt;
        var canonical = String(readConfig(&#039;wgCanonicalSpecialPageName&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var allowed = {&lt;br /&gt;
            userlogin: true,&lt;br /&gt;
            passwordreset: true,&lt;br /&gt;
            resetpass: true,&lt;br /&gt;
            confirmemail: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (allowed[canonical]) return true;&lt;br /&gt;
        return /^(?:special|특수):(?:userlogin|로그인|passwordreset|비밀번호 ?재설정|resetpass|confirmemail)/i.test(name);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requiresLoginGate() {&lt;br /&gt;
        return isAnonymousUser() &amp;amp;&amp;amp; !isAuthenticationPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBootExcludedPage() {&lt;br /&gt;
        var ns = Number(readConfig(&#039;wgNamespaceNumber&#039;, NaN));&lt;br /&gt;
        var action = String(readConfig(&#039;wgAction&#039;, &#039;view&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var model = String(readConfig(&#039;wgPageContentModel&#039;, &#039;&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var name = normalizeBootPageName(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || window.location.pathname || &#039;&#039;);&lt;br /&gt;
        var systemNamespaces = {&lt;br /&gt;
            &#039;-1&#039;: true, &#039;4&#039;: true, &#039;5&#039;: true, &#039;6&#039;: true, &#039;7&#039;: true, &#039;8&#039;: true, &#039;9&#039;: true,&lt;br /&gt;
            &#039;10&#039;: true, &#039;11&#039;: true, &#039;12&#039;: true, &#039;13&#039;: true, &#039;14&#039;: true, &#039;15&#039;: true,&lt;br /&gt;
            &#039;828&#039;: true, &#039;829&#039;: true&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * The public boot gate is for normal reading surfaces.  Developer/system&lt;br /&gt;
         * namespaces and edit/diff/history actions already reload outside SPA, so&lt;br /&gt;
         * blocking them would turn every maintenance save into another entry boot.&lt;br /&gt;
         */&lt;br /&gt;
        if (hasQueryFlag(&#039;bootGatePreview&#039;, &#039;1&#039;)) return false;&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return true;&lt;br /&gt;
        if (systemNamespaces[String(ns)]) return true;&lt;br /&gt;
        if (model === &#039;css&#039; || model === &#039;javascript&#039; || model === &#039;json&#039; || model === &#039;sanitized-css&#039;) return true;&lt;br /&gt;
        if (/\.(?:css|js|json)$/i.test(name)) return true;&lt;br /&gt;
        if (/^(?:mediawiki|미디어위키|file|파일|project|프로젝트|template|틀|module|모듈|category|분류|special|특수):/i.test(name)) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var LOGIN_REQUIRED = requiresLoginGate();&lt;br /&gt;
    var BOOT_EXCLUDED_PAGE = isBootExcludedPage() &amp;amp;&amp;amp; !LOGIN_REQUIRED;&lt;br /&gt;
&lt;br /&gt;
    function injectEarlyBootStyle() {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (earlyBootStyleInjected || !document.documentElement) return;&lt;br /&gt;
        earlyBootStyleInjected = true;&lt;br /&gt;
        style = document.createElement(&#039;style&#039;);&lt;br /&gt;
        style.id = &#039;boot-gate-early-style&#039;;&lt;br /&gt;
        style.textContent = [&lt;br /&gt;
            &#039;html.boot-gate-active, body.boot-gate-active{overflow:hidden!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body{background:#080808!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active body&amp;gt; :not(#boot-gate-screen){visibility:hidden!important;pointer-events:none!important;}&#039;,&lt;br /&gt;
            &#039;html.boot-gate-active #boot-gate-screen,html.boot-gate-active #boot-gate-screen *{visibility:visible!important;}&#039;&lt;br /&gt;
        ].join(&#039;&#039;);&lt;br /&gt;
        (document.head || document.documentElement).appendChild(style);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function activateBootSurface() {&lt;br /&gt;
        injectEarlyBootStyle();&lt;br /&gt;
        if (document.documentElement) document.documentElement.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
        if (document.body) document.body.classList.add(&#039;boot-gate-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!BOOT_EXCLUDED_PAGE) activateBootSurface();&lt;br /&gt;
    else if (window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.release) window.__BootGatePrelude.release();&lt;br /&gt;
&lt;br /&gt;
    var defaultManifest = {&lt;br /&gt;
        version: &#039;20260710-globe-vhs-restore-entry-001&#039;,&lt;br /&gt;
        boot: {&lt;br /&gt;
            minDisplayMs: 950,&lt;br /&gt;
            cachedMinDisplayMs: 350,&lt;br /&gt;
            maxBlockingMs: 15000&lt;br /&gt;
        },&lt;br /&gt;
        initial: {&lt;br /&gt;
            full: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;decorations-registry&#039;,&lt;br /&gt;
                    label: &#039;DECORATION REGISTRY&#039;,&lt;br /&gt;
                    type: &#039;decorations&#039;,&lt;br /&gt;
                    ref: &#039;MediaWiki:Decorations.json&#039;,&lt;br /&gt;
                    page: &#039;시대&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    preparePixels: true&lt;br /&gt;
                },&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1950-entry&#039;,&lt;br /&gt;
                    label: &#039;1950 ERA ENTRY&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1950&#039;,&lt;br /&gt;
                    level: &#039;full&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ],&lt;br /&gt;
            half: [&lt;br /&gt;
                {&lt;br /&gt;
                    id: &#039;nations-1960-half&#039;,&lt;br /&gt;
                    label: &#039;1960 ERA HALF&#039;,&lt;br /&gt;
                    type: &#039;nations-era&#039;,&lt;br /&gt;
                    era: &#039;1960&#039;,&lt;br /&gt;
                    level: &#039;half&#039;&lt;br /&gt;
                }&lt;br /&gt;
            ]&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function now() {&lt;br /&gt;
        return Date.now ? Date.now() : new Date().getTime();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var BootPerf = window.BootPerf = window.BootPerf || (function () {&lt;br /&gt;
        var t0 = now();&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;entry&#039;), start: now(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = now();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = ended - item.start;&lt;br /&gt;
            item.offset = item.start - t0;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            entries.push(item);&lt;br /&gt;
            return item;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function instant(name, meta) {&lt;br /&gt;
            var t = now();&lt;br /&gt;
            entries.push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: t - t0, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measure(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function rows() {&lt;br /&gt;
            return entries.slice().sort(function (a, b) { return a.start - b.start; }).map(function (item) {&lt;br /&gt;
                return {&lt;br /&gt;
                    offset: item.offset,&lt;br /&gt;
                    ms: item.ms,&lt;br /&gt;
                    name: item.name,&lt;br /&gt;
                    meta: item.meta || {}&lt;br /&gt;
                };&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            var total = list.reduce(function (max, item) { return Math.max(max, item.offset + item.ms); }, 0);&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD_ID,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: total,&lt;br /&gt;
                entries: list,&lt;br /&gt;
                active: Object.keys(active).map(function (id) { return active[id]; })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function print() {&lt;br /&gt;
            var list = rows();&lt;br /&gt;
            if (!window.console || !console.log) return summary();&lt;br /&gt;
            try {&lt;br /&gt;
                console.groupCollapsed(&#039;[BootPerf] entry loading timeline · &#039; + BUILD_ID);&lt;br /&gt;
                if (console.table) console.table(list.map(function (item) {&lt;br /&gt;
                    return {&lt;br /&gt;
                        offset: item.offset + &#039;ms&#039;,&lt;br /&gt;
                        duration: item.ms + &#039;ms&#039;,&lt;br /&gt;
                        name: item.name,&lt;br /&gt;
                        detail: JSON.stringify(item.meta || {})&lt;br /&gt;
                    };&lt;br /&gt;
                }));&lt;br /&gt;
                else list.forEach(function (item) { console.log(item.offset + &#039;ms&#039;, item.ms + &#039;ms&#039;, item.name, item.meta || {}); });&lt;br /&gt;
                console.groupEnd();&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            return summary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: instant,&lt;br /&gt;
            measure: measure,&lt;br /&gt;
            rows: rows,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            print: print&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var InteractionPerf = window.InteractionPerf = window.InteractionPerf || (function () {&lt;br /&gt;
        var BUILD = &#039;20260710-interaction-perf-instrument-001&#039;;&lt;br /&gt;
        var t0 = (window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now());&lt;br /&gt;
        var entries = [];&lt;br /&gt;
        var active = {};&lt;br /&gt;
        var seq = 0;&lt;br /&gt;
        var maxEntries = 1600;&lt;br /&gt;
&lt;br /&gt;
        function perfNow() {&lt;br /&gt;
            return window.performance &amp;amp;&amp;amp; performance.now ? performance.now() : now();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cloneMeta(meta) {&lt;br /&gt;
            var out = {};&lt;br /&gt;
            Object.keys(meta || {}).forEach(function (key) {&lt;br /&gt;
                var value = meta[key];&lt;br /&gt;
                if (value == null) return;&lt;br /&gt;
                if (typeof value === &#039;string&#039; || typeof value === &#039;number&#039; || typeof value === &#039;boolean&#039;) out[key] = value;&lt;br /&gt;
                else {&lt;br /&gt;
                    try { out[key] = JSON.parse(JSON.stringify(value)); }&lt;br /&gt;
                    catch (err) { out[key] = String(value); }&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function push(entry) {&lt;br /&gt;
            entries.push(entry);&lt;br /&gt;
            if (entries.length &amp;gt; maxEntries) entries.splice(0, entries.length - maxEntries);&lt;br /&gt;
            return entry;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function start(name, meta) {&lt;br /&gt;
            var id = String(++seq);&lt;br /&gt;
            active[id] = { id: id, name: String(name || &#039;interaction&#039;), start: perfNow(), meta: cloneMeta(meta) };&lt;br /&gt;
            return id;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function end(id, meta) {&lt;br /&gt;
            var item = active[id];&lt;br /&gt;
            var ended = perfNow();&lt;br /&gt;
            if (!item) return null;&lt;br /&gt;
            delete active[id];&lt;br /&gt;
            item.end = ended;&lt;br /&gt;
            item.ms = Math.round((ended - item.start) * 100) / 100;&lt;br /&gt;
            item.offset = Math.round((item.start - t0) * 100) / 100;&lt;br /&gt;
            item.meta = Object.assign({}, item.meta || {}, cloneMeta(meta));&lt;br /&gt;
            return push(item);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function mark(name, meta) {&lt;br /&gt;
            var t = perfNow();&lt;br /&gt;
            return push({ id: String(++seq), name: String(name || &#039;mark&#039;), start: t, end: t, ms: 0, offset: Math.round((t - t0) * 100) / 100, meta: cloneMeta(meta) });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureSync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                var value = fn();&lt;br /&gt;
                end(id, { ok: true });&lt;br /&gt;
                return value;&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                throw err;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function measureAsync(name, meta, fn) {&lt;br /&gt;
            var id = start(name, meta);&lt;br /&gt;
            try {&lt;br /&gt;
                return Promise.resolve(fn()).then(function (value) {&lt;br /&gt;
                    end(id, { ok: true });&lt;br /&gt;
                    return value;&lt;br /&gt;
                }, function (err) {&lt;br /&gt;
                    end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                    throw err;&lt;br /&gt;
                });&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                end(id, { ok: false, error: err &amp;amp;&amp;amp; (err.message || String(err)) || String(err) });&lt;br /&gt;
                return Promise.reject(err);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function summary() {&lt;br /&gt;
            return {&lt;br /&gt;
                build: BUILD,&lt;br /&gt;
                startedAt: t0,&lt;br /&gt;
                totalMs: Math.round((perfNow() - t0) * 100) / 100,&lt;br /&gt;
                entries: entries.slice().sort(function (a, b) {&lt;br /&gt;
                    if (a.offset !== b.offset) return a.offset - b.offset;&lt;br /&gt;
                    return b.ms - a.ms;&lt;br /&gt;
                }),&lt;br /&gt;
                active: Object.keys(active).map(function (id) {&lt;br /&gt;
                    var item = active[id];&lt;br /&gt;
                    return { offset: Math.round((item.start - t0) * 100) / 100, ms: Math.round((perfNow() - item.start) * 100) / 100, name: item.name, meta: item.meta || {} };&lt;br /&gt;
                })&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function table() {&lt;br /&gt;
            var data = summary().entries.map(function (entry) {&lt;br /&gt;
                return { offset: entry.offset, ms: entry.ms, name: entry.name, meta: entry.meta };&lt;br /&gt;
            });&lt;br /&gt;
            if (window.console &amp;amp;&amp;amp; console.table) console.table(data);&lt;br /&gt;
            return data;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.PerformanceObserver &amp;amp;&amp;amp; !window.CLBI_InteractionLongTaskObserverBound) {&lt;br /&gt;
                window.CLBI_InteractionLongTaskObserverBound = true;&lt;br /&gt;
                new PerformanceObserver(function (list) {&lt;br /&gt;
                    list.getEntries().forEach(function (entry) {&lt;br /&gt;
                        mark(&#039;browser long task&#039;, {&lt;br /&gt;
                            ms: Math.round(entry.duration * 100) / 100,&lt;br /&gt;
                            start: Math.round(entry.startTime * 100) / 100,&lt;br /&gt;
                            attribution: entry.attribution &amp;amp;&amp;amp; entry.attribution.length ? entry.attribution.length : 0&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }).observe({ entryTypes: [&#039;longtask&#039;] });&lt;br /&gt;
            }&lt;br /&gt;
        } catch (ignoreLongTaskObserver) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD,&lt;br /&gt;
            start: start,&lt;br /&gt;
            end: end,&lt;br /&gt;
            mark: mark,&lt;br /&gt;
            measureSync: measureSync,&lt;br /&gt;
            measureAsync: measureAsync,&lt;br /&gt;
            summary: summary,&lt;br /&gt;
            table: table&lt;br /&gt;
        };&lt;br /&gt;
    }());&lt;br /&gt;
&lt;br /&gt;
    function toArray(value) {&lt;br /&gt;
        return Array.prototype.slice.call(value || []);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function unique(list) {&lt;br /&gt;
        var seen = {};&lt;br /&gt;
        var out = [];&lt;br /&gt;
        (list || []).forEach(function (item) {&lt;br /&gt;
            item = String(item || &#039;&#039;).trim();&lt;br /&gt;
            if (!item || seen[item]) return;&lt;br /&gt;
            seen[item] = true;&lt;br /&gt;
            out.push(item);&lt;br /&gt;
        });&lt;br /&gt;
        return out;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasBootParam(value) {&lt;br /&gt;
        var search = String(window.location &amp;amp;&amp;amp; window.location.search || &#039;&#039;);&lt;br /&gt;
        var re = new RegExp(&#039;[?&amp;amp;]&#039; + DISMISS_PARAM + &#039;=([^&amp;amp;]+)&#039;);&lt;br /&gt;
        var match = search.match(re);&lt;br /&gt;
        return match &amp;amp;&amp;amp; decodeURIComponent(match[1]) === value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeTitle(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;#&#039;)[0]&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function extractTitleFromUrl(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;);&lt;br /&gt;
        var match = text.match(/[?&amp;amp;]title=([^&amp;amp;]+)/i);&lt;br /&gt;
        if (match) return normalizeTitle(decodeURIComponent(match[1].replace(/\+/g, &#039; &#039;)));&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeRefKey(ref) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        title = extractTitleFromUrl(text);&lt;br /&gt;
        if (title) return &#039;title:&#039; + title.toLowerCase();&lt;br /&gt;
        if (text.indexOf(&#039;/&#039;) === -1 &amp;amp;&amp;amp; text.indexOf(&#039;:&#039;) !== -1) return &#039;title:&#039; + normalizeTitle(text).toLowerCase();&lt;br /&gt;
        return &#039;url:&#039; + text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rawUrlForRef(ref, ctype) {&lt;br /&gt;
        var text = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        var title;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(text) || text.charAt(0) === &#039;/&#039;) return text;&lt;br /&gt;
        title = normalizeTitle(text.indexOf(&#039;:&#039;) !== -1 ? text : (&#039;MediaWiki:&#039; + text));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            return mw.util.getUrl(title, { action: &#039;raw&#039;, ctype: ctype || &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=&#039; + encodeURIComponent(ctype || &#039;application/json&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getApiEndpoint() {&lt;br /&gt;
        return (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.wikiScript === &#039;function&#039;) ? mw.util.wikiScript(&#039;api&#039;) : &#039;/api.php&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchApi(params) {&lt;br /&gt;
        var body = new URLSearchParams();&lt;br /&gt;
        Object.keys(params || {}).forEach(function (key) {&lt;br /&gt;
            body.append(key, params[key]);&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(getApiEndpoint(), {&lt;br /&gt;
            method: &#039;POST&#039;,&lt;br /&gt;
            credentials: &#039;same-origin&#039;,&lt;br /&gt;
            headers: { &#039;Content-Type&#039;: &#039;application/x-www-form-urlencoded; charset=UTF-8&#039; },&lt;br /&gt;
            body: body.toString()&lt;br /&gt;
        }).then(function (res) {&lt;br /&gt;
            if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
            return res.json();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /* =========================================&lt;br /&gt;
       Revision manifest and persistent entry cache&lt;br /&gt;
       =========================================&lt;br /&gt;
       The first boot still prepares current-tab artifacts, but freshness is not guessed from&lt;br /&gt;
       filenames or old localStorage flags.  The client reads a tiny server-side current-state&lt;br /&gt;
       manifest, compares page revisions / file sha1 values, and only keeps cached raw resources&lt;br /&gt;
       whose revision token still matches the server.  The manifest is a latest-state table, not&lt;br /&gt;
       an append-only client log.&lt;br /&gt;
    */&lt;br /&gt;
    var REVISION_MANIFEST_ACTION = &#039;entryrevisionmanifest&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_LOCAL_KEY = &#039;entry-revision-manifest-current-v1&#039;;&lt;br /&gt;
    var REVISION_MANIFEST_PRUNE_LOCAL_KEY = &#039;entry-revision-manifest-last-client-prune-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_NAME = &#039;entry-cache-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_INDEX_KEY = &#039;entry-cache-index-v1&#039;;&lt;br /&gt;
    var ENTRY_CACHE_REQUEST_PREFIX = &#039;/__entry-cache__/&#039;;&lt;br /&gt;
    var ENTRY_CACHE_PACK_KEY = &#039;entry-cache-pack-state-v1&#039;;&lt;br /&gt;
&lt;br /&gt;
    function hasCacheStorage() {&lt;br /&gt;
        return !!(window.caches &amp;amp;&amp;amp; typeof window.caches.open === &#039;function&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function safeJsonParse(text, fallback) {&lt;br /&gt;
        try { return JSON.parse(text); } catch (err) { return fallback; }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readLocalJson(key, fallback) {&lt;br /&gt;
        try {&lt;br /&gt;
            var text = window.localStorage ? window.localStorage.getItem(key) : null;&lt;br /&gt;
            return text ? safeJsonParse(text, fallback) : fallback;&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return fallback;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function writeLocalJson(key, value) {&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.localStorage) window.localStorage.setItem(key, JSON.stringify(value));&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeManifestTitle(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var match;&lt;br /&gt;
        var i;&lt;br /&gt;
        if (!text) return &#039;&#039;;&lt;br /&gt;
        for (i = 0; i &amp;lt; 3; i += 1) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
            } catch (err) { break; }&lt;br /&gt;
        }&lt;br /&gt;
        match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
        if (match) text = match[1];&lt;br /&gt;
        text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^\/+/, &#039;&#039;)&lt;br /&gt;
            .replace(/^index\.php\/?/i, &#039;&#039;)&lt;br /&gt;
            .replace(/^wiki\/?/i, &#039;&#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
        match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/([^?#]+)(?:[?#].*)?$/i);&lt;br /&gt;
        if (match) text = &#039;File:&#039; + match[1];&lt;br /&gt;
        text = text.split(&#039;#&#039;)[0].replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
        text = text.replace(/^(?:파일|Image|이미지)\s*:/i, &#039;File:&#039;);&lt;br /&gt;
        if (/^(?:mediawiki|template|module|manage|file)\s*:/i.test(text)) {&lt;br /&gt;
            text = text.replace(/^([^:]+)\s*:\s*/, function (_, ns) { return ns.charAt(0).toUpperCase() + ns.slice(1).toLowerCase() + &#039;:&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
        if (/^File:/i.test(text)) text = &#039;File:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        if (/^Mediawiki:/i.test(text)) text = &#039;MediaWiki:&#039; + text.slice(text.indexOf(&#039;:&#039;) + 1).trim();&lt;br /&gt;
        return text;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resourceToken(resource) {&lt;br /&gt;
        if (!resource || typeof resource !== &#039;object&#039;) return &#039;&#039;;&lt;br /&gt;
        return String(resource.revision || resource.sha1 || resource.hash || resource.timestamp || resource.updatedAt || resource.url || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createEntryCache() {&lt;br /&gt;
        var index = readLocalJson(ENTRY_CACHE_INDEX_KEY, { entries: {} }) || { entries: {} };&lt;br /&gt;
        var packs = readLocalJson(ENTRY_CACHE_PACK_KEY, { packs: {} }) || { packs: {} };&lt;br /&gt;
        var objectUrls = {};&lt;br /&gt;
        var stats = {&lt;br /&gt;
            textHits: 0,&lt;br /&gt;
            blobHits: 0,&lt;br /&gt;
            misses: 0,&lt;br /&gt;
            networkStores: 0,&lt;br /&gt;
            stores: 0,&lt;br /&gt;
            deletes: 0&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        function ensureIndex() {&lt;br /&gt;
            if (!index || typeof index !== &#039;object&#039;) index = { entries: {} };&lt;br /&gt;
            if (!index.entries || typeof index.entries !== &#039;object&#039;) index.entries = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactIndexForLocalStorage() {&lt;br /&gt;
            var entries;&lt;br /&gt;
            var next = {};&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var entry = entries[key] || {};&lt;br /&gt;
                var kind = String(entry.kind || &#039;raw&#039;).toLowerCase();&lt;br /&gt;
                /*&lt;br /&gt;
                 * CacheStorage already owns image/blob bodies.  Keeping one localStorage&lt;br /&gt;
                 * metadata row per flag/blob competes with MediaWiki ResourceLoader&#039;s own&lt;br /&gt;
                 * localStorage module store and can trigger QuotaExceededError before our&lt;br /&gt;
                 * code even runs on the next page load.  Text/json/file URL rows stay indexed&lt;br /&gt;
                 * because they are small and useful for invalidation diagnostics.&lt;br /&gt;
                 */&lt;br /&gt;
                if (kind === &#039;image&#039; || kind === &#039;blob&#039;) return;&lt;br /&gt;
                next[key] = entry;&lt;br /&gt;
            });&lt;br /&gt;
            index.entries = next;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensurePacks() {&lt;br /&gt;
            if (!packs || typeof packs !== &#039;object&#039;) packs = { packs: {} };&lt;br /&gt;
            if (!packs.packs || typeof packs.packs !== &#039;object&#039;) packs.packs = {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function saveIndex() {&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            compactIndexForLocalStorage();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_INDEX_KEY, index);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function savePacks() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            writeLocalJson(ENTRY_CACHE_PACK_KEY, packs);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function requestForKey(key) {&lt;br /&gt;
            return new Request(ENTRY_CACHE_REQUEST_PREFIX + encodeURIComponent(String(key || &#039;&#039;)), { credentials: &#039;same-origin&#039; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function openCache() {&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return window.caches.open(ENTRY_CACHE_NAME).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheEntry(key, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            index.entries[key] = Object.assign({}, index.entries[key] || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                resourceKey: String(meta &amp;amp;&amp;amp; meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                token: String(meta &amp;amp;&amp;amp; meta.token || &#039;&#039;),&lt;br /&gt;
                kind: String(meta &amp;amp;&amp;amp; meta.kind || &#039;raw&#039;),&lt;br /&gt;
                contentType: String(meta &amp;amp;&amp;amp; meta.contentType || &#039;&#039;),&lt;br /&gt;
                size: Number(meta &amp;amp;&amp;amp; meta.size || 0) || null,&lt;br /&gt;
                createdAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].createdAt ? index.entries[key].createdAt : now(),&lt;br /&gt;
                updatedAt: now(),&lt;br /&gt;
                lastHitAt: index.entries[key] &amp;amp;&amp;amp; index.entries[key].lastHitAt ? index.entries[key].lastHitAt : null,&lt;br /&gt;
                hits: Number(index.entries[key] &amp;amp;&amp;amp; index.entries[key].hits || 0)&lt;br /&gt;
            });&lt;br /&gt;
            saveIndex();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function markHit(key, field) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            if (index.entries[key]) {&lt;br /&gt;
                index.entries[key].hits = Number(index.entries[key].hits || 0) + 1;&lt;br /&gt;
                index.entries[key].lastHitAt = now();&lt;br /&gt;
                saveIndex();&lt;br /&gt;
            }&lt;br /&gt;
            if (field &amp;amp;&amp;amp; stats[field] != null) stats[field] += 1;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getResponse(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(null);&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return null;&lt;br /&gt;
                return cache.match(requestForKey(key)).then(function (res) {&lt;br /&gt;
                    if (!res) {&lt;br /&gt;
                        stats.misses += 1;&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }&lt;br /&gt;
                    return res;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putResponse(key, response, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !response || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var cloned;&lt;br /&gt;
                var headers;&lt;br /&gt;
                var bodyPromise;&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                cloned = response.clone();&lt;br /&gt;
                bodyPromise = cloned.blob().catch(function () { return null; });&lt;br /&gt;
                return bodyPromise.then(function (blob) {&lt;br /&gt;
                    if (!blob) return false;&lt;br /&gt;
                    headers = new Headers(response.headers || {});&lt;br /&gt;
                    if (!headers.get(&#039;Content-Type&#039;)) headers.set(&#039;Content-Type&#039;, meta.contentType || blob.type || &#039;application/octet-stream&#039;);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cache-Key&#039;, key);&lt;br /&gt;
                    headers.set(&#039;X-Entry-Resource-Key&#039;, String(meta.resourceKey || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Revision-Token&#039;, String(meta.token || &#039;&#039;));&lt;br /&gt;
                    headers.set(&#039;X-Entry-Cached-At&#039;, String(now()));&lt;br /&gt;
                    return cache.put(requestForKey(key), new Response(blob, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                        stats.stores += 1;&lt;br /&gt;
                        cacheEntry(key, {&lt;br /&gt;
                            resourceKey: meta.resourceKey,&lt;br /&gt;
                            token: meta.token,&lt;br /&gt;
                            kind: meta.kind || &#039;blob&#039;,&lt;br /&gt;
                            contentType: headers.get(&#039;Content-Type&#039;) || blob.type || &#039;&#039;,&lt;br /&gt;
                            size: blob.size&lt;br /&gt;
                        });&lt;br /&gt;
                        return true;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getText(key) {&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return null;&lt;br /&gt;
                markHit(key, &#039;textHits&#039;);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).catch(function () { return null; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function putText(key, text, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            meta = meta || {};&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                var headers;&lt;br /&gt;
                var body = String(text || &#039;&#039;);&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                headers = new Headers({&lt;br /&gt;
                    &#039;Content-Type&#039;: meta.contentType || &#039;text/plain; charset=UTF-8&#039;,&lt;br /&gt;
                    &#039;X-Entry-Cache-Key&#039;: key,&lt;br /&gt;
                    &#039;X-Entry-Resource-Key&#039;: String(meta.resourceKey || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Revision-Token&#039;: String(meta.token || &#039;&#039;),&lt;br /&gt;
                    &#039;X-Entry-Cached-At&#039;: String(now())&lt;br /&gt;
                });&lt;br /&gt;
                return cache.put(requestForKey(key), new Response(body, { status: 200, headers: headers })).then(function () {&lt;br /&gt;
                    stats.stores += 1;&lt;br /&gt;
                    cacheEntry(key, {&lt;br /&gt;
                        resourceKey: meta.resourceKey,&lt;br /&gt;
                        token: meta.token,&lt;br /&gt;
                        kind: meta.kind || &#039;text&#039;,&lt;br /&gt;
                        contentType: headers.get(&#039;Content-Type&#039;),&lt;br /&gt;
                        size: body.length&lt;br /&gt;
                    });&lt;br /&gt;
                    return true;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getBlobUrl(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                return Promise.resolve(objectUrls[key]);&lt;br /&gt;
            }&lt;br /&gt;
            return getResponse(key).then(function (res) {&lt;br /&gt;
                if (!res) return &#039;&#039;;&lt;br /&gt;
                return res.blob().then(function (blob) {&lt;br /&gt;
                    if (!blob || !blob.size) return &#039;&#039;;&lt;br /&gt;
                    objectUrls[key] = URL.createObjectURL(blob);&lt;br /&gt;
                    markHit(key, &#039;blobHits&#039;);&lt;br /&gt;
                    return objectUrls[key];&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchBlobUrl(url, key, meta, options) {&lt;br /&gt;
            url = String(url || &#039;&#039;).trim();&lt;br /&gt;
            key = String(key || &#039;&#039;).trim();&lt;br /&gt;
            if (!url || !key) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            return getBlobUrl(key).then(function (cachedUrl) {&lt;br /&gt;
                if (cachedUrl) return cachedUrl;&lt;br /&gt;
                return fetch(url, {&lt;br /&gt;
                    credentials: &#039;same-origin&#039;,&lt;br /&gt;
                    cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
                }).then(function (res) {&lt;br /&gt;
                    if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                    stats.networkStores += 1;&lt;br /&gt;
                    return putResponse(key, res, Object.assign({}, meta || {}, { kind: meta &amp;amp;&amp;amp; meta.kind ? meta.kind : &#039;blob&#039; })).then(function () {&lt;br /&gt;
                        return getBlobUrl(key);&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return &#039;&#039;;&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function deleteKey(key) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            if (!key || !hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            if (objectUrls[key]) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
                delete objectUrls[key];&lt;br /&gt;
            }&lt;br /&gt;
            return openCache().then(function (cache) {&lt;br /&gt;
                if (!cache) return false;&lt;br /&gt;
                return cache.delete(requestForKey(key)).then(function (ok) {&lt;br /&gt;
                    ensureIndex();&lt;br /&gt;
                    if (index.entries &amp;amp;&amp;amp; index.entries[key]) {&lt;br /&gt;
                        delete index.entries[key];&lt;br /&gt;
                        stats.deletes += 1;&lt;br /&gt;
                        saveIndex();&lt;br /&gt;
                    }&lt;br /&gt;
                    return ok;&lt;br /&gt;
                });&lt;br /&gt;
            }).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function invalidateResources(resourceKeys) {&lt;br /&gt;
            var map = {};&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys = [];&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            (resourceKeys || []).forEach(function (resourceKey) {&lt;br /&gt;
                resourceKey = String(resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (resourceKey) map[resourceKey] = true;&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(entries).forEach(function (cacheKey) {&lt;br /&gt;
                var resourceKey = String(entries[cacheKey] &amp;amp;&amp;amp; entries[cacheKey].resourceKey || &#039;&#039;).toLowerCase();&lt;br /&gt;
                if (map[resourceKey]) keys.push(cacheKey);&lt;br /&gt;
            });&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function prune(maxAgeDays) {&lt;br /&gt;
            var days = Math.max(1, Number(maxAgeDays) || 7);&lt;br /&gt;
            var cutoff = now() - days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var keys;&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            keys = Object.keys(entries).filter(function (key) {&lt;br /&gt;
                return Number(entries[key] &amp;amp;&amp;amp; entries[key].createdAt || 0) &amp;lt; cutoff;&lt;br /&gt;
            });&lt;br /&gt;
            window.localStorage &amp;amp;&amp;amp; window.localStorage.setItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY, String(now()));&lt;br /&gt;
            return Promise.all(keys.map(deleteKey)).then(function () { return keys.length; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function reset() {&lt;br /&gt;
            Object.keys(objectUrls).forEach(function (key) {&lt;br /&gt;
                try { URL.revokeObjectURL(objectUrls[key]); } catch (err) {}&lt;br /&gt;
            });&lt;br /&gt;
            objectUrls = {};&lt;br /&gt;
            index = { entries: {} };&lt;br /&gt;
            packs = { packs: {} };&lt;br /&gt;
            saveIndex();&lt;br /&gt;
            savePacks();&lt;br /&gt;
            if (!hasCacheStorage()) return Promise.resolve(false);&lt;br /&gt;
            return window.caches.delete(ENTRY_CACHE_NAME).catch(function () { return false; });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packReady(key, token) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            return !!(packs.packs[key] &amp;amp;&amp;amp; String(packs.packs[key].token || &#039;&#039;) === token &amp;amp;&amp;amp; packs.packs[key].ready);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setPackReady(key, token, meta) {&lt;br /&gt;
            key = String(key || &#039;&#039;);&lt;br /&gt;
            token = String(token || &#039;&#039;);&lt;br /&gt;
            if (!key || !token) return false;&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            packs.packs[key] = Object.assign({}, meta || {}, {&lt;br /&gt;
                key: key,&lt;br /&gt;
                token: token,&lt;br /&gt;
                ready: true,&lt;br /&gt;
                updatedAt: now()&lt;br /&gt;
            });&lt;br /&gt;
            savePacks();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function packInfo() {&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            return Object.assign({}, packs.packs || {});&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function info() {&lt;br /&gt;
            var lastClientPrune = 0;&lt;br /&gt;
            var entries;&lt;br /&gt;
            var byKind = {};&lt;br /&gt;
            try { lastClientPrune = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            ensureIndex();&lt;br /&gt;
            ensurePacks();&lt;br /&gt;
            entries = index.entries || {};&lt;br /&gt;
            Object.keys(entries).forEach(function (key) {&lt;br /&gt;
                var kind = String(entries[key] &amp;amp;&amp;amp; entries[key].kind || &#039;raw&#039;);&lt;br /&gt;
                byKind[kind] = (byKind[kind] || 0) + 1;&lt;br /&gt;
            });&lt;br /&gt;
            return {&lt;br /&gt;
                supported: hasCacheStorage(),&lt;br /&gt;
                name: ENTRY_CACHE_NAME,&lt;br /&gt;
                entries: Object.keys(entries).length,&lt;br /&gt;
                byKind: byKind,&lt;br /&gt;
                packs: Object.keys(packs.packs || {}).length,&lt;br /&gt;
                lastClientPruneAt: lastClientPrune || null,&lt;br /&gt;
                stats: Object.assign({}, stats)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Trim legacy image/blob metadata rows produced by older builds. */&lt;br /&gt;
        try { saveIndex(); } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            getResponse: getResponse,&lt;br /&gt;
            putResponse: putResponse,&lt;br /&gt;
            getText: getText,&lt;br /&gt;
            putText: putText,&lt;br /&gt;
            getBlobUrl: getBlobUrl,&lt;br /&gt;
            fetchBlobUrl: fetchBlobUrl,&lt;br /&gt;
            invalidateResources: invalidateResources,&lt;br /&gt;
            prune: prune,&lt;br /&gt;
            reset: reset,&lt;br /&gt;
            packReady: packReady,&lt;br /&gt;
            setPackReady: setPackReady,&lt;br /&gt;
            packInfo: packInfo,&lt;br /&gt;
            info: info&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryCache = window.EntryCache || createEntryCache();&lt;br /&gt;
&lt;br /&gt;
    function createRevisionManifestService() {&lt;br /&gt;
        var current = null;&lt;br /&gt;
        var previous = readLocalJson(REVISION_MANIFEST_LOCAL_KEY, null);&lt;br /&gt;
        var loadPromise = null;&lt;br /&gt;
        var changedResources = [];&lt;br /&gt;
&lt;br /&gt;
        function unwrap(payload) {&lt;br /&gt;
            return payload &amp;amp;&amp;amp; (payload.entryrevisionmanifest || payload.revisionManifest || payload) || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourcesOf(manifest) {&lt;br /&gt;
            return manifest &amp;amp;&amp;amp; manifest.resources &amp;amp;&amp;amp; typeof manifest.resources === &#039;object&#039; ? manifest.resources : {};&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function compactRevisionManifest(manifest) {&lt;br /&gt;
            var out;&lt;br /&gt;
            var resources;&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039;) return null;&lt;br /&gt;
            out = {&lt;br /&gt;
                manifestVersion: manifest.manifestVersion || manifest.version || &#039;&#039;,&lt;br /&gt;
                version: manifest.version || manifest.manifestVersion || &#039;&#039;,&lt;br /&gt;
                generatedAt: manifest.generatedAt || &#039;&#039;,&lt;br /&gt;
                pruneDays: manifest.pruneDays,&lt;br /&gt;
                clientPruneDays: manifest.clientPruneDays,&lt;br /&gt;
                resources: {}&lt;br /&gt;
            };&lt;br /&gt;
            resources = resourcesOf(manifest);&lt;br /&gt;
            Object.keys(resources).forEach(function (title) {&lt;br /&gt;
                var src = resources[title];&lt;br /&gt;
                var dst;&lt;br /&gt;
                if (!src || typeof src !== &#039;object&#039;) return;&lt;br /&gt;
                dst = {};&lt;br /&gt;
                [&#039;revision&#039;, &#039;sha1&#039;, &#039;hash&#039;, &#039;timestamp&#039;, &#039;updatedAt&#039;, &#039;url&#039;].forEach(function (key) {&lt;br /&gt;
                    if (src[key] !== undefined &amp;amp;&amp;amp; src[key] !== null &amp;amp;&amp;amp; String(src[key]) !== &#039;&#039;) dst[key] = src[key];&lt;br /&gt;
                });&lt;br /&gt;
                out.resources[title] = dst;&lt;br /&gt;
            });&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function buildLookup(manifest) {&lt;br /&gt;
            var lookup = {};&lt;br /&gt;
            Object.keys(resourcesOf(manifest)).forEach(function (title) {&lt;br /&gt;
                lookup[normalizeManifestTitle(title).toLowerCase()] = resourcesOf(manifest)[title];&lt;br /&gt;
            });&lt;br /&gt;
            return lookup;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function computeChanged(prev, next) {&lt;br /&gt;
            var prevLookup = buildLookup(prev);&lt;br /&gt;
            var nextLookup = buildLookup(next);&lt;br /&gt;
            var out = [];&lt;br /&gt;
            Object.keys(nextLookup).forEach(function (key) {&lt;br /&gt;
                if (resourceToken(prevLookup[key]) !== resourceToken(nextLookup[key])) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            Object.keys(prevLookup).forEach(function (key) {&lt;br /&gt;
                if (!nextLookup[key]) out.push(key);&lt;br /&gt;
            });&lt;br /&gt;
            return unique(out);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function load(options) {&lt;br /&gt;
            if (loadPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return loadPromise;&lt;br /&gt;
            loadPromise = fetchApi({&lt;br /&gt;
                action: REVISION_MANIFEST_ACTION,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;&lt;br /&gt;
            }).then(function (payload) {&lt;br /&gt;
                var manifest = unwrap(payload);&lt;br /&gt;
                if (!manifest || !manifest.resources) throw new Error(&#039;invalid revision manifest&#039;);&lt;br /&gt;
                current = compactRevisionManifest(manifest) || manifest;&lt;br /&gt;
                changedResources = computeChanged(previous, current);&lt;br /&gt;
                if (changedResources.length &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.invalidateResources === &#039;function&#039;) {&lt;br /&gt;
                    window.EntryCache.invalidateResources(changedResources);&lt;br /&gt;
                }&lt;br /&gt;
                writeLocalJson(REVISION_MANIFEST_LOCAL_KEY, current);&lt;br /&gt;
                previous = current;&lt;br /&gt;
                maybePrune();&lt;br /&gt;
                return current;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                current = previous || null;&lt;br /&gt;
                return current;&lt;br /&gt;
            });&lt;br /&gt;
            return loadPromise;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function ensureLoaded() {&lt;br /&gt;
            return current ? Promise.resolve(current) : load();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            var lookup;&lt;br /&gt;
            if (!title || !current) return null;&lt;br /&gt;
            lookup = buildLookup(current);&lt;br /&gt;
            return lookup[title.toLowerCase()] || null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenForRef(ref) {&lt;br /&gt;
            return resourceToken(resourceForRef(ref));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resourceKeyForRef(ref) {&lt;br /&gt;
            var title = normalizeManifestTitle(ref);&lt;br /&gt;
            return title ? title.toLowerCase() : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForRef(ref, type) {&lt;br /&gt;
            var resourceKey = resourceKeyForRef(ref);&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            if (!resourceKey || !token) return &#039;&#039;;&lt;br /&gt;
            return String(type || &#039;raw&#039;) + &#039;:&#039; + resourceKey + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function manifestToken(extra) {&lt;br /&gt;
            var base = current &amp;amp;&amp;amp; (current.manifestVersion || current.version || current.generatedAt || &#039;&#039;) || &#039;&#039;;&lt;br /&gt;
            return String(base || &#039;no-manifest&#039;) + (extra ? (&#039;:&#039; + String(extra)) : &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function tokenFromUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;);&lt;br /&gt;
            var match = text.match(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=([^&amp;amp;]+)/);&lt;br /&gt;
            if (!match) return &#039;&#039;;&lt;br /&gt;
            try { return decodeURIComponent(match[1]); } catch (err) { return match[1]; }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheKeyForUrl(url, type) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var token = tokenFromUrl(text) || manifestToken(&#039;url&#039;);&lt;br /&gt;
            var normalized;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                normalized = new URL(text, window.location.href);&lt;br /&gt;
                text = normalized.pathname + (normalized.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            text = text.replace(/[?&amp;amp;](?:_entryFileRev|_entryAsset|_entryRev)=[^&amp;amp;]*/g, &#039;&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            try { text = decodeURI(text); } catch (err2) {}&lt;br /&gt;
            return String(type || &#039;blob&#039;) + &#039;:url:&#039; + text.toLowerCase() + &#039;@&#039; + token;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function addRevisionParam(url, ref) {&lt;br /&gt;
            var token = tokenForRef(ref);&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var sep;&lt;br /&gt;
            if (!text || !token || /[?&amp;amp;]_entryRev=/.test(text)) return text;&lt;br /&gt;
            sep = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + sep + &#039;_entryRev=&#039; + encodeURIComponent(token);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function maybePrune() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var due;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            due = !last || (now() - last &amp;gt;= days * 24 * 60 * 60 * 1000);&lt;br /&gt;
            if (due &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.prune === &#039;function&#039;) {&lt;br /&gt;
                window.EntryCache.prune(days);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function countdown() {&lt;br /&gt;
            var days = current &amp;amp;&amp;amp; Number(current.pruneDays || current.clientPruneDays) || 7;&lt;br /&gt;
            var last = 0;&lt;br /&gt;
            var next;&lt;br /&gt;
            var remain;&lt;br /&gt;
            try { last = Number(window.localStorage &amp;amp;&amp;amp; window.localStorage.getItem(REVISION_MANIFEST_PRUNE_LOCAL_KEY) || 0); } catch (err) {}&lt;br /&gt;
            if (!last) return { days: days, lastClientPruneAt: null, nextClientPruneAt: null, remainingMs: 0, remainingDays: 0, due: true };&lt;br /&gt;
            next = last + days * 24 * 60 * 60 * 1000;&lt;br /&gt;
            remain = Math.max(0, next - now());&lt;br /&gt;
            return {&lt;br /&gt;
                days: days,&lt;br /&gt;
                lastClientPruneAt: last,&lt;br /&gt;
                nextClientPruneAt: next,&lt;br /&gt;
                remainingMs: remain,&lt;br /&gt;
                remainingDays: Math.ceil(remain / (24 * 60 * 60 * 1000)),&lt;br /&gt;
                due: remain &amp;lt;= 0&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function status() {&lt;br /&gt;
            return {&lt;br /&gt;
                available: !!current,&lt;br /&gt;
                manifestVersion: current &amp;amp;&amp;amp; (current.manifestVersion || current.version || &#039;&#039;),&lt;br /&gt;
                generatedAt: current &amp;amp;&amp;amp; current.generatedAt || &#039;&#039;,&lt;br /&gt;
                changedResources: changedResources.slice(0, 50),&lt;br /&gt;
                changedCount: changedResources.length,&lt;br /&gt;
                prune: countdown(),&lt;br /&gt;
                cache: window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.info === &#039;function&#039; ? window.EntryCache.info() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            load: load,&lt;br /&gt;
            ensureLoaded: ensureLoaded,&lt;br /&gt;
            current: function () { return current; },&lt;br /&gt;
            resourceForRef: resourceForRef,&lt;br /&gt;
            tokenForRef: tokenForRef,&lt;br /&gt;
            cacheKeyForRef: cacheKeyForRef,&lt;br /&gt;
            cacheKeyForUrl: cacheKeyForUrl,&lt;br /&gt;
            manifestToken: manifestToken,&lt;br /&gt;
            resourceKeyForRef: resourceKeyForRef,&lt;br /&gt;
            addRevisionParam: addRevisionParam,&lt;br /&gt;
            maybePrune: maybePrune,&lt;br /&gt;
            countdown: countdown,&lt;br /&gt;
            status: status&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.RevisionManifest = window.RevisionManifest || createRevisionManifestService();&lt;br /&gt;
&lt;br /&gt;
    function createEntryStore() {&lt;br /&gt;
        var jsonCache = {};&lt;br /&gt;
        var flagUrlCache = {};&lt;br /&gt;
        var fileUrlCache = {};&lt;br /&gt;
        var imageReadyCache = {};&lt;br /&gt;
        var imageObjectCache = {};&lt;br /&gt;
        var imageDisplayUrlCache = {};&lt;br /&gt;
        var imagePromiseCache = {};&lt;br /&gt;
        var rawPromiseCache = {};&lt;br /&gt;
        var textCache = {};&lt;br /&gt;
        var textPromiseCache = {};&lt;br /&gt;
&lt;br /&gt;
        function fetchJsonRef(ref, options) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var promiseKey;&lt;br /&gt;
            var url;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty json ref&#039;));&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(ref, &#039;json&#039;) : &#039;&#039;;&lt;br /&gt;
            promiseKey = cacheKey ? (key + &#039;@&#039; + cacheKey) : key;&lt;br /&gt;
            if (jsonCache[promiseKey]) return Promise.resolve(jsonCache[promiseKey].data);&lt;br /&gt;
            if (rawPromiseCache[promiseKey]) return rawPromiseCache[promiseKey];&lt;br /&gt;
            url = rawUrlForRef(ref, &#039;application/json&#039;);&lt;br /&gt;
            if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
            }&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(ref) : key;&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(ref) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                rawPromiseCache[promiseKey] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        var cachedData = cachedText &amp;amp;&amp;amp; cachedText.trim() ? JSON.parse(cachedText) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: cachedData, loadedAt: now(), persistent: true };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        return cachedData;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                        jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                        jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token });&lt;br /&gt;
                        return data;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return rawPromiseCache[promiseKey];&lt;br /&gt;
            }&lt;br /&gt;
            rawPromiseCache[promiseKey] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                var data = text &amp;amp;&amp;amp; text.trim() ? JSON.parse(text) : {};&lt;br /&gt;
                jsonCache[promiseKey] = { key: promiseKey, ref: ref, url: url, data: data, loadedAt: now() };&lt;br /&gt;
                jsonCache[key] = jsonCache[promiseKey];&lt;br /&gt;
                return data;&lt;br /&gt;
            });&lt;br /&gt;
            return rawPromiseCache[promiseKey];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getJsonSync(ref) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            return key &amp;amp;&amp;amp; jsonCache[key] ? jsonCache[key].data : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setJsonRef(ref, data) {&lt;br /&gt;
            var key = normalizeRefKey(ref);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            jsonCache[key] = { key: key, ref: ref, url: rawUrlForRef(ref, &#039;application/json&#039;), data: data, loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeFile(value) {&lt;br /&gt;
            return String(value || &#039;&#039;)&lt;br /&gt;
                .replace(/^(?:file|파일):/i, &#039;&#039;)&lt;br /&gt;
                .trim();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileKey(value) {&lt;br /&gt;
            return normalizeFile(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setFlagUrl(file, url) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            flagUrlCache[key] = String(url || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getFlagUrl(file) {&lt;br /&gt;
            var key = fileKey(file);&lt;br /&gt;
            return key ? (flagUrlCache[key] || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFlagUrls(files) {&lt;br /&gt;
            var clean = unique((files || []).map(normalizeFile).filter(Boolean));&lt;br /&gt;
&lt;br /&gt;
            function cacheKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;flagurl&#039;) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function resourceKeyForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + fileKey(file));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function tokenForFile(file) {&lt;br /&gt;
                return window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function hydrateCachedFlagUrl(file) {&lt;br /&gt;
                var key = fileKey(file);&lt;br /&gt;
                var cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                if (!key || flagUrlCache[key] !== undefined || !cacheKey || !window.EntryCache || typeof window.EntryCache.getText !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.resolve(false);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        flagUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return true;&lt;br /&gt;
                    }&lt;br /&gt;
                    return false;&lt;br /&gt;
                }).catch(function () { return false; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(clean.map(hydrateCachedFlagUrl)).then(function () {&lt;br /&gt;
                var pending = clean.filter(function (file) {&lt;br /&gt;
                    return getFlagUrl(file) === &#039;&#039; &amp;amp;&amp;amp; flagUrlCache[fileKey(file)] === undefined;&lt;br /&gt;
                });&lt;br /&gt;
                var chunks = [];&lt;br /&gt;
&lt;br /&gt;
                while (pending.length) chunks.push(pending.splice(0, 20));&lt;br /&gt;
                if (!chunks.length) return flagUrlCache;&lt;br /&gt;
&lt;br /&gt;
                return Promise.all(chunks.map(function (chunk) {&lt;br /&gt;
                    var titleToKey = {};&lt;br /&gt;
                    var keyToFile = {};&lt;br /&gt;
                    var titles = [];&lt;br /&gt;
                    chunk.forEach(function (file) {&lt;br /&gt;
                        var key = fileKey(file);&lt;br /&gt;
                        if (!key) return;&lt;br /&gt;
                        titleToKey[fileKey(&#039;File:&#039; + file)] = key;&lt;br /&gt;
                        titleToKey[fileKey(&#039;파일:&#039; + file)] = key;&lt;br /&gt;
                        keyToFile[key] = file;&lt;br /&gt;
                        titles.push(&#039;File:&#039; + file);&lt;br /&gt;
                        titles.push(&#039;파일:&#039; + file);&lt;br /&gt;
                    });&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size&#039;,&lt;br /&gt;
                        iiurlwidth: &#039;16&#039;,&lt;br /&gt;
                        titles: titles.join(&#039;|&#039;)&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var seen = {};&lt;br /&gt;
                        pages.forEach(function (page) {&lt;br /&gt;
                            var key = titleToKey[fileKey(page &amp;amp;&amp;amp; page.title)] || fileKey(page &amp;amp;&amp;amp; page.title);&lt;br /&gt;
                            var file = keyToFile[key] || (page &amp;amp;&amp;amp; page.title || &#039;&#039;).replace(/^(?:File|파일):/i, &#039;&#039;);&lt;br /&gt;
                            var imageinfo = page &amp;amp;&amp;amp; page.imageinfo &amp;amp;&amp;amp; page.imageinfo[0];&lt;br /&gt;
                            var url;&lt;br /&gt;
                            var rev;&lt;br /&gt;
                            var sep;&lt;br /&gt;
                            var cacheKey;&lt;br /&gt;
                            if (!key) return;&lt;br /&gt;
                            seen[key] = true;&lt;br /&gt;
                            url = imageinfo &amp;amp;&amp;amp; (imageinfo.thumburl || imageinfo.url) ? (imageinfo.thumburl || imageinfo.url) : &#039;&#039;;&lt;br /&gt;
                            if (url) {&lt;br /&gt;
                                rev = imageinfo &amp;amp;&amp;amp; (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) ? (imageinfo.sha1 || imageinfo.timestamp || imageinfo.size) : &#039;&#039;;&lt;br /&gt;
                                if (rev &amp;amp;&amp;amp; !/[?&amp;amp;]_entryFileRev=/.test(url)) {&lt;br /&gt;
                                    sep = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                                    url += sep + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                                }&lt;br /&gt;
                            }&lt;br /&gt;
                            flagUrlCache[key] = url;&lt;br /&gt;
                            cacheKey = cacheKeyForFile(file);&lt;br /&gt;
                            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.putText === &#039;function&#039;) {&lt;br /&gt;
                                window.EntryCache.putText(cacheKey, url || &#039;&#039;, {&lt;br /&gt;
                                    resourceKey: resourceKeyForFile(file),&lt;br /&gt;
                                    token: tokenForFile(file),&lt;br /&gt;
                                    kind: &#039;flagurl&#039;,&lt;br /&gt;
                                    contentType: &#039;text/plain; charset=UTF-8&#039;&lt;br /&gt;
                                });&lt;br /&gt;
                            }&lt;br /&gt;
                        });&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; !seen[key] &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        chunk.forEach(function (file) {&lt;br /&gt;
                            var key = fileKey(file);&lt;br /&gt;
                            if (key &amp;amp;&amp;amp; flagUrlCache[key] === undefined) flagUrlCache[key] = &#039;&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () { return flagUrlCache; });&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeGenericFileTitle(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            var match;&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            for (i = 0; i &amp;lt; 4; i += 1) {&lt;br /&gt;
                try {&lt;br /&gt;
                    if (/%[0-9a-f]{2}/i.test(text)) text = decodeURIComponent(text);&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    break;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            match = text.match(/[?&amp;amp;]title=([^&amp;amp;#]+)/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^https?:\/\/[^/]+/i, &#039;&#039;).replace(/^\/+/, &#039;&#039;).replace(/^index\.php\/?/i, &#039;&#039;).replace(/^wiki\/?/i, &#039;&#039;).trim();&lt;br /&gt;
            match = text.match(/(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\/(.+)$/i);&lt;br /&gt;
            if (match) text = match[1];&lt;br /&gt;
            text = text.replace(/^(?:File|파일|Image|이미지)\s*:/i, &#039;&#039;).replace(/^:+/, &#039;&#039;).trim();&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isFileRef(value) {&lt;br /&gt;
            var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
            return /^(?:file|파일)\s*:/i.test(text) || /(?:^|\/)(?:(?:Special|특수)\s*[:/]\s*)?(?:Redirect|넘겨주기)\s*\/\s*file\s*\//i.test(text);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fileUrlKey(value) {&lt;br /&gt;
            return normalizeGenericFileTitle(value).replace(/_/g, &#039; &#039;).replace(/\s+/g, &#039; &#039;).toLowerCase();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function stableDirectImageUrl(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var separator;&lt;br /&gt;
            var bust;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            if (/[?&amp;amp;]_entryAsset=/.test(text) || /[?&amp;amp;]_=/.test(text)) return text;&lt;br /&gt;
            bust = String(BUILD_ID || &#039;entry&#039;);&lt;br /&gt;
            separator = text.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
            return text + separator + &#039;_entryAsset=&#039; + encodeURIComponent(bust);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function resolveFileUrl(ref) {&lt;br /&gt;
            var original = String(ref || &#039;&#039;).trim();&lt;br /&gt;
            var file;&lt;br /&gt;
            var key;&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!original) return Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (!isFileRef(original)) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            file = normalizeGenericFileTitle(original);&lt;br /&gt;
            key = fileUrlKey(file);&lt;br /&gt;
            if (!file || !key) return Promise.resolve(stableDirectImageUrl(original));&lt;br /&gt;
            if (fileUrlCache[key]) return Promise.resolve(fileUrlCache[key]);&lt;br /&gt;
            cacheKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(&#039;File:&#039; + file, &#039;fileurl&#039;) : &#039;&#039;;&lt;br /&gt;
            resourceKey = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(&#039;File:&#039; + file) : (&#039;file:&#039; + key);&lt;br /&gt;
            token = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(&#039;File:&#039; + file) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                return window.EntryCache.getText(cacheKey).then(function (cachedUrl) {&lt;br /&gt;
                    if (cachedUrl !== null &amp;amp;&amp;amp; cachedUrl !== undefined) {&lt;br /&gt;
                        fileUrlCache[key] = String(cachedUrl || &#039;&#039;);&lt;br /&gt;
                        return fileUrlCache[key];&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetchApi({&lt;br /&gt;
                        action: &#039;query&#039;,&lt;br /&gt;
                        format: &#039;json&#039;,&lt;br /&gt;
                        formatversion: &#039;2&#039;,&lt;br /&gt;
                        redirects: &#039;1&#039;,&lt;br /&gt;
                        prop: &#039;imageinfo&#039;,&lt;br /&gt;
                        iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                        titles: &#039;File:&#039; + file&lt;br /&gt;
                    }).then(function (json) {&lt;br /&gt;
                        var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                        var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                        var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                        var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                        var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                        var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                        fileUrlCache[key] = resolved;&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, resolved, { resourceKey: resourceKey, token: token, kind: &#039;fileurl&#039;, contentType: &#039;text/plain; charset=UTF-8&#039; });&lt;br /&gt;
                        return resolved;&lt;br /&gt;
                    });&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    return stableDirectImageUrl(original);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
            return fetchApi({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: &#039;2&#039;,&lt;br /&gt;
                redirects: &#039;1&#039;,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url|sha1|timestamp|size|mime&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + file&lt;br /&gt;
            }).then(function (json) {&lt;br /&gt;
                var pages = (json &amp;amp;&amp;amp; json.query &amp;amp;&amp;amp; json.query.pages) || [];&lt;br /&gt;
                var info = pages[0] &amp;amp;&amp;amp; pages[0].imageinfo &amp;amp;&amp;amp; pages[0].imageinfo[0] ? pages[0].imageinfo[0] : null;&lt;br /&gt;
                var url = info &amp;amp;&amp;amp; info.url ? info.url : original;&lt;br /&gt;
                var rev = info &amp;amp;&amp;amp; (info.sha1 || info.timestamp || info.size) ? (info.sha1 || info.timestamp || info.size) : String(BUILD_ID || &#039;entry-file&#039;);&lt;br /&gt;
                var separator = url.indexOf(&#039;?&#039;) === -1 ? &#039;?&#039; : &#039;&amp;amp;&#039;;&lt;br /&gt;
                var resolved = url + separator + &#039;_entryFileRev=&#039; + encodeURIComponent(String(rev));&lt;br /&gt;
                fileUrlCache[key] = resolved;&lt;br /&gt;
                return resolved;&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                return stableDirectImageUrl(original);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImageUrl(url, options) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var sourcePromise;&lt;br /&gt;
            var persistent = !(options &amp;amp;&amp;amp; options.persistent === false);&lt;br /&gt;
            if (!key) return Promise.resolve(false);&lt;br /&gt;
            if (imageReadyCache[key]) return Promise.resolve(true);&lt;br /&gt;
            if (imagePromiseCache[key]) return imagePromiseCache[key];&lt;br /&gt;
&lt;br /&gt;
            cacheKey = persistent &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039; ? window.RevisionManifest.cacheKeyForUrl(key, &#039;image&#039;) : &#039;&#039;;&lt;br /&gt;
            sourcePromise = Promise.resolve(&#039;&#039;);&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.fetchBlobUrl === &#039;function&#039;) {&lt;br /&gt;
                sourcePromise = window.EntryCache.fetchBlobUrl(key, cacheKey, {&lt;br /&gt;
                    resourceKey: &#039;&#039;,&lt;br /&gt;
                    token: &#039;&#039;,&lt;br /&gt;
                    kind: &#039;image&#039;&lt;br /&gt;
                }).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            imagePromiseCache[key] = sourcePromise.then(function (cachedObjectUrl) {&lt;br /&gt;
                return new Promise(function (resolve) {&lt;br /&gt;
                    var img = new Image();&lt;br /&gt;
                    var settled = false;&lt;br /&gt;
                    var src = cachedObjectUrl || key;&lt;br /&gt;
                    function finish(ok) {&lt;br /&gt;
                        if (settled) return;&lt;br /&gt;
                        settled = true;&lt;br /&gt;
                        if (ok) {&lt;br /&gt;
                            imageReadyCache[key] = true;&lt;br /&gt;
                            imageReadyCache[src] = true;&lt;br /&gt;
                            imageObjectCache[key] = img;&lt;br /&gt;
                            imageObjectCache[src] = img;&lt;br /&gt;
                            imageDisplayUrlCache[key] = src;&lt;br /&gt;
                            imageDisplayUrlCache[src] = src;&lt;br /&gt;
                        }&lt;br /&gt;
                        resolve(!!ok);&lt;br /&gt;
                    }&lt;br /&gt;
                    img.onload = function () {&lt;br /&gt;
                        if (img.decode) {&lt;br /&gt;
                            img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        } else {&lt;br /&gt;
                            finish(true);&lt;br /&gt;
                        }&lt;br /&gt;
                    };&lt;br /&gt;
                    img.onerror = function () {&lt;br /&gt;
                        if (src !== key) {&lt;br /&gt;
                            src = key;&lt;br /&gt;
                            img.src = key;&lt;br /&gt;
                            return;&lt;br /&gt;
                        }&lt;br /&gt;
                        finish(false);&lt;br /&gt;
                    };&lt;br /&gt;
                    img.decoding = &#039;async&#039;;&lt;br /&gt;
                    img.loading = &#039;eager&#039;;&lt;br /&gt;
                    img.src = src;&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        if (img.decode) img.decode().then(function () { finish(true); }).catch(function () { finish(true); });&lt;br /&gt;
                        else finish(true);&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
            return imagePromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function preloadImages(urls, options) {&lt;br /&gt;
            var list = unique((urls || []).filter(Boolean));&lt;br /&gt;
            var limit = options &amp;amp;&amp;amp; Number(options.limit);&lt;br /&gt;
            var concurrency = Math.max(1, Math.min(48, Number(options &amp;amp;&amp;amp; options.concurrency) || 24));&lt;br /&gt;
            var index = 0;&lt;br /&gt;
            var ok = 0;&lt;br /&gt;
            var fail = 0;&lt;br /&gt;
            if (Number.isFinite(limit) &amp;amp;&amp;amp; limit &amp;gt; 0) list = list.slice(0, limit);&lt;br /&gt;
            if (!list.length) return Promise.resolve({ total: 0, ok: 0, fail: 0 });&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                function pump() {&lt;br /&gt;
                    while (index &amp;lt; list.length &amp;amp;&amp;amp; concurrency &amp;gt; 0) {&lt;br /&gt;
                        (function (url) {&lt;br /&gt;
                            concurrency -= 1;&lt;br /&gt;
                            preloadImageUrl(url, options || {}).then(function (result) {&lt;br /&gt;
                                if (result) ok += 1;&lt;br /&gt;
                                else fail += 1;&lt;br /&gt;
                            }).catch(function () {&lt;br /&gt;
                                fail += 1;&lt;br /&gt;
                            }).then(function () {&lt;br /&gt;
                                concurrency += 1;&lt;br /&gt;
                                if (index &amp;gt;= list.length &amp;amp;&amp;amp; ok + fail &amp;gt;= list.length) resolve({ total: list.length, ok: ok, fail: fail });&lt;br /&gt;
                                else pump();&lt;br /&gt;
                            });&lt;br /&gt;
                        })(list[index++]);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                pump();&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function isImageReady(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return !!(key &amp;amp;&amp;amp; imageReadyCache[key]);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageElement(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageObjectCache[key] || null) : null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getImageDisplayUrl(url) {&lt;br /&gt;
            var key = String(url || &#039;&#039;).trim();&lt;br /&gt;
            return key ? (imageDisplayUrlCache[key] || key) : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function normalizeUrlKey(url) {&lt;br /&gt;
            var text = String(url || &#039;&#039;).trim();&lt;br /&gt;
            var a;&lt;br /&gt;
            if (!text) return &#039;&#039;;&lt;br /&gt;
            try {&lt;br /&gt;
                a = document.createElement(&#039;a&#039;);&lt;br /&gt;
                a.href = text;&lt;br /&gt;
                text = a.pathname + (a.search || &#039;&#039;);&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
            try {&lt;br /&gt;
                text = decodeURI(text);&lt;br /&gt;
            } catch (err2) {}&lt;br /&gt;
            text = text.replace(/([?&amp;amp;])_=[^&amp;amp;]*/g, &#039;$1&#039;).replace(/[?&amp;amp;]$/, &#039;&#039;);&lt;br /&gt;
            text = text.replace(/_/g, &#039;_&#039;);&lt;br /&gt;
            return text;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function fetchTextUrl(url, options) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            var cacheKey;&lt;br /&gt;
            var resourceRef;&lt;br /&gt;
            var resourceKey;&lt;br /&gt;
            var token;&lt;br /&gt;
            if (!key) return Promise.reject(new Error(&#039;empty text url&#039;));&lt;br /&gt;
            if (textCache[key]) return Promise.resolve(textCache[key].text);&lt;br /&gt;
            if (textPromiseCache[key]) return textPromiseCache[key];&lt;br /&gt;
            resourceRef = options &amp;amp;&amp;amp; options.resourceRef ? options.resourceRef : (options &amp;amp;&amp;amp; options.ref ? options.ref : &#039;&#039;);&lt;br /&gt;
            cacheKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForRef === &#039;function&#039; ? window.RevisionManifest.cacheKeyForRef(resourceRef, &#039;text&#039;) : &#039;&#039;;&lt;br /&gt;
            if (!cacheKey &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.cacheKeyForUrl === &#039;function&#039;) cacheKey = window.RevisionManifest.cacheKeyForUrl(url, &#039;text&#039;);&lt;br /&gt;
            resourceKey = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.resourceKeyForRef === &#039;function&#039; ? window.RevisionManifest.resourceKeyForRef(resourceRef) : (&#039;url:&#039; + key);&lt;br /&gt;
            token = resourceRef &amp;amp;&amp;amp; window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.tokenForRef === &#039;function&#039; ? window.RevisionManifest.tokenForRef(resourceRef) : &#039;&#039;;&lt;br /&gt;
            if (cacheKey &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.noStore) &amp;amp;&amp;amp; window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.getText === &#039;function&#039;) {&lt;br /&gt;
                textPromiseCache[key] = window.EntryCache.getText(cacheKey).then(function (cachedText) {&lt;br /&gt;
                    if (cachedText !== null &amp;amp;&amp;amp; cachedText !== undefined) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: cachedText, loadedAt: now(), persistent: true };&lt;br /&gt;
                        return cachedText;&lt;br /&gt;
                    }&lt;br /&gt;
                    return fetch(url, {&lt;br /&gt;
                        credentials: &#039;same-origin&#039;,&lt;br /&gt;
                        cache: &#039;force-cache&#039;&lt;br /&gt;
                    }).then(function (res) {&lt;br /&gt;
                        if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                        return res.text();&lt;br /&gt;
                    }).then(function (text) {&lt;br /&gt;
                        textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                        window.EntryCache.putText(cacheKey, text, { resourceKey: resourceKey, token: token, kind: &#039;text&#039;, contentType: &#039;text/html; charset=UTF-8&#039; });&lt;br /&gt;
                        return text;&lt;br /&gt;
                    });&lt;br /&gt;
                });&lt;br /&gt;
                return textPromiseCache[key];&lt;br /&gt;
            }&lt;br /&gt;
            textPromiseCache[key] = fetch(url, {&lt;br /&gt;
                credentials: &#039;same-origin&#039;,&lt;br /&gt;
                cache: options &amp;amp;&amp;amp; options.noStore ? &#039;no-store&#039; : &#039;force-cache&#039;&lt;br /&gt;
            }).then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;HTTP &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            }).then(function (text) {&lt;br /&gt;
                textCache[key] = { key: key, url: url, text: text, loadedAt: now() };&lt;br /&gt;
                return text;&lt;br /&gt;
            });&lt;br /&gt;
            return textPromiseCache[key];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function getTextSync(url) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            return key &amp;amp;&amp;amp; textCache[key] ? textCache[key].text : &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function setTextUrl(url, text) {&lt;br /&gt;
            var key = normalizeUrlKey(url);&lt;br /&gt;
            if (!key) return;&lt;br /&gt;
            textCache[key] = { key: key, url: url, text: String(text || &#039;&#039;), loadedAt: now() };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        function cacheInfo() {&lt;br /&gt;
            return {&lt;br /&gt;
                json: Object.keys(jsonCache).length,&lt;br /&gt;
                jsonKeys: Object.keys(jsonCache),&lt;br /&gt;
                flags: Object.keys(flagUrlCache).length,&lt;br /&gt;
                files: Object.keys(fileUrlCache).length,&lt;br /&gt;
                images: Object.keys(imageReadyCache).length,&lt;br /&gt;
                retainedImages: Object.keys(imageObjectCache).length,&lt;br /&gt;
                displayImages: Object.keys(imageDisplayUrlCache).length,&lt;br /&gt;
                text: Object.keys(textCache).length,&lt;br /&gt;
                textKeys: Object.keys(textCache)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            fetchJsonRef: fetchJsonRef,&lt;br /&gt;
            getJsonSync: getJsonSync,&lt;br /&gt;
            setJsonRef: setJsonRef,&lt;br /&gt;
            normalizeRefKey: normalizeRefKey,&lt;br /&gt;
            rawUrlForRef: rawUrlForRef,&lt;br /&gt;
            revisionUrlForRef: function (ref, ctype) {&lt;br /&gt;
                var url = rawUrlForRef(ref, ctype);&lt;br /&gt;
                if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
                    url = window.RevisionManifest.addRevisionParam(url, ref);&lt;br /&gt;
                }&lt;br /&gt;
                return url;&lt;br /&gt;
            },&lt;br /&gt;
            resolveFlagUrls: resolveFlagUrls,&lt;br /&gt;
            setFlagUrl: setFlagUrl,&lt;br /&gt;
            getFlagUrl: getFlagUrl,&lt;br /&gt;
            resolveFileUrl: resolveFileUrl,&lt;br /&gt;
            preloadImageUrl: preloadImageUrl,&lt;br /&gt;
            preloadImages: preloadImages,&lt;br /&gt;
            isImageReady: isImageReady,&lt;br /&gt;
            getImageElement: getImageElement,&lt;br /&gt;
            getImageDisplayUrl: getImageDisplayUrl,&lt;br /&gt;
            fetchTextUrl: fetchTextUrl,&lt;br /&gt;
            getTextSync: getTextSync,&lt;br /&gt;
            setTextUrl: setTextUrl,&lt;br /&gt;
            stableDirectImageUrl: stableDirectImageUrl,&lt;br /&gt;
            cacheInfo: cacheInfo&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryStore = window.EntryStore || createEntryStore();&lt;br /&gt;
&lt;br /&gt;
    function updateBootProgress(done, total, label) {&lt;br /&gt;
        var pct = total ? Math.max(0, Math.min(100, Math.round((done / total) * 100))) : 0;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = pct + &#039;%&#039;;&lt;br /&gt;
        if (bootDetailNode &amp;amp;&amp;amp; label) bootDetailNode.textContent = label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function adoptBootScreen(node) {&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        bootNode = node;&lt;br /&gt;
        bootStatusNode = bootNode.querySelector(&#039;.boot-gate-status&#039;);&lt;br /&gt;
        bootProgressNode = bootNode.querySelector(&#039;.boot-gate-progress&#039;);&lt;br /&gt;
        bootDetailNode = bootNode.querySelector(&#039;.boot-gate-detail&#039;);&lt;br /&gt;
        bootFillNode = bootNode.querySelector(&#039;.boot-gate-meter-fill&#039;);&lt;br /&gt;
        bootNode.classList.add(&#039;is-active&#039;);&lt;br /&gt;
        bootNode.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureBootScreen(options) {&lt;br /&gt;
        var panel;&lt;br /&gt;
        var header;&lt;br /&gt;
        var meter;&lt;br /&gt;
        var existing;&lt;br /&gt;
        var decoLayer;&lt;br /&gt;
        var close;&lt;br /&gt;
        var previewMode;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        previewMode = !!options.preview;&lt;br /&gt;
        if (bootNode &amp;amp;&amp;amp; bootNode.parentNode) return bootNode;&lt;br /&gt;
        if (!document.body) return null;&lt;br /&gt;
&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !previewMode) return null;&lt;br /&gt;
        if (!previewMode) activateBootSurface();&lt;br /&gt;
&lt;br /&gt;
        existing = document.getElementById(&#039;boot-gate-screen&#039;) || (!previewMode &amp;amp;&amp;amp; window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.ensure ? window.__BootGatePrelude.ensure() : null);&lt;br /&gt;
        if (existing) return adoptBootScreen(existing);&lt;br /&gt;
&lt;br /&gt;
        bootNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootNode.id = &#039;boot-gate-screen&#039;;&lt;br /&gt;
        bootNode.className = &#039;boot-gate-screen is-active&#039;;&lt;br /&gt;
        bootNode.setAttribute(&#039;role&#039;, &#039;status&#039;);&lt;br /&gt;
        bootNode.setAttribute(&#039;aria-live&#039;, &#039;polite&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
        panel.className = &#039;boot-gate-panel&#039;;&lt;br /&gt;
&lt;br /&gt;
        header = document.createElement(&#039;div&#039;);&lt;br /&gt;
        header.className = &#039;boot-gate-title&#039;;&lt;br /&gt;
        header.textContent = &#039;ARCHIVE INITIALIZATION&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootStatusNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootStatusNode.className = &#039;boot-gate-status&#039;;&lt;br /&gt;
        bootStatusNode.textContent = &#039;Preparing entry systems&#039;;&lt;br /&gt;
&lt;br /&gt;
        meter = document.createElement(&#039;div&#039;);&lt;br /&gt;
        meter.className = &#039;boot-gate-meter&#039;;&lt;br /&gt;
        bootFillNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootFillNode.className = &#039;boot-gate-meter-fill&#039;;&lt;br /&gt;
        meter.appendChild(bootFillNode);&lt;br /&gt;
&lt;br /&gt;
        bootProgressNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootProgressNode.className = &#039;boot-gate-progress&#039;;&lt;br /&gt;
        bootProgressNode.textContent = &#039;0%&#039;;&lt;br /&gt;
&lt;br /&gt;
        bootDetailNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bootDetailNode.className = &#039;boot-gate-detail&#039;;&lt;br /&gt;
        bootDetailNode.textContent = &#039;loading manifest&#039;;&lt;br /&gt;
&lt;br /&gt;
        close = document.createElement(&#039;button&#039;);&lt;br /&gt;
        close.type = &#039;button&#039;;&lt;br /&gt;
        close.className = &#039;boot-gate-close&#039;;&lt;br /&gt;
        close.setAttribute(&#039;aria-label&#039;, &#039;Close boot preview&#039;);&lt;br /&gt;
        close.textContent = &#039;×&#039;;&lt;br /&gt;
        close.addEventListener(&#039;click&#039;, function () { hideBootScreen({ force: true }); });&lt;br /&gt;
&lt;br /&gt;
        decoLayer = document.createElement(&#039;div&#039;);&lt;br /&gt;
        decoLayer.className = &#039;boot-gate-decoration-layer&#039;;&lt;br /&gt;
        decoLayer.setAttribute(&#039;data-decoration-target&#039;, &#039;boot-gate&#039;);&lt;br /&gt;
        decoLayer.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel.appendChild(header);&lt;br /&gt;
        panel.appendChild(bootStatusNode);&lt;br /&gt;
        panel.appendChild(meter);&lt;br /&gt;
        panel.appendChild(bootProgressNode);&lt;br /&gt;
        panel.appendChild(bootDetailNode);&lt;br /&gt;
        bootNode.appendChild(decoLayer);&lt;br /&gt;
        bootNode.appendChild(panel);&lt;br /&gt;
        bootNode.appendChild(close);&lt;br /&gt;
        document.body.appendChild(bootNode);&lt;br /&gt;
        return bootNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function buildLoginUrl() {&lt;br /&gt;
        var pageName = String(readConfig(&#039;wgPageName&#039;, &#039;&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        var params = {};&lt;br /&gt;
&lt;br /&gt;
        if (pageName &amp;amp;&amp;amp; !isAuthenticationPage() &amp;amp;&amp;amp; !isCreateAccountPage()) params.returnto = pageName;&lt;br /&gt;
        try {&lt;br /&gt;
            if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                return mw.util.getUrl(&#039;Special:UserLogin&#039;, params);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
        return &#039;/index.php?title=Special%3AUserLogin&#039; + (params.returnto ? &#039;&amp;amp;returnto=&#039; + encodeURIComponent(params.returnto) : &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureLoginGateAction(panel) {&lt;br /&gt;
        var copy;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (loginGateActionNode &amp;amp;&amp;amp; loginGateActionNode.parentNode) return loginGateActionNode;&lt;br /&gt;
        loginGateActionNode = document.createElement(&#039;div&#039;);&lt;br /&gt;
        loginGateActionNode.className = &#039;boot-gate-login&#039;;&lt;br /&gt;
&lt;br /&gt;
        copy = document.createElement(&#039;div&#039;);&lt;br /&gt;
        copy.className = &#039;boot-gate-login-copy&#039;;&lt;br /&gt;
        copy.textContent = &#039;SIGN IN TO ENTER THE WIKI.&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = document.createElement(&#039;a&#039;);&lt;br /&gt;
        action.className = &#039;boot-gate-login-action&#039;;&lt;br /&gt;
        action.href = buildLoginUrl();&lt;br /&gt;
        action.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        action.setAttribute(&#039;role&#039;, &#039;button&#039;);&lt;br /&gt;
&lt;br /&gt;
        loginGateActionNode.appendChild(copy);&lt;br /&gt;
        loginGateActionNode.appendChild(action);&lt;br /&gt;
        panel.appendChild(loginGateActionNode);&lt;br /&gt;
        return loginGateActionNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showLoginGate() {&lt;br /&gt;
        var node = ensureBootScreen();&lt;br /&gt;
        var panel;&lt;br /&gt;
        var title;&lt;br /&gt;
        var action;&lt;br /&gt;
&lt;br /&gt;
        if (!node || !requiresLoginGate()) return false;&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        loginGateLocked = true;&lt;br /&gt;
        node.classList.remove(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.add(&#039;is-active&#039;, &#039;is-login-required&#039;);&lt;br /&gt;
        node.setAttribute(&#039;role&#039;, &#039;dialog&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-modal&#039;, &#039;true&#039;);&lt;br /&gt;
        node.setAttribute(&#039;aria-live&#039;, &#039;off&#039;);&lt;br /&gt;
&lt;br /&gt;
        panel = node.querySelector(&#039;.boot-gate-panel&#039;);&lt;br /&gt;
        title = node.querySelector(&#039;.boot-gate-title&#039;);&lt;br /&gt;
        if (title) title.textContent = &#039;ACCOUNT AUTHENTICATION&#039;;&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = &#039;LOGIN REQUIRED&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootProgressNode) bootProgressNode.textContent = &#039;&#039;;&lt;br /&gt;
        if (bootFillNode) bootFillNode.style.width = &#039;100%&#039;;&lt;br /&gt;
&lt;br /&gt;
        action = panel ? ensureLoginGateAction(panel).querySelector(&#039;.boot-gate-login-action&#039;) : null;&lt;br /&gt;
        if (action) {&lt;br /&gt;
            action.href = buildLoginUrl();&lt;br /&gt;
            window.requestAnimationFrame(function () {&lt;br /&gt;
                try { action.focus({ preventScroll: true }); }&lt;br /&gt;
                catch (err) { try { action.focus(); } catch (ignore) {} }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hideBootScreen(options) {&lt;br /&gt;
        var node = bootNode || document.getElementById(&#039;boot-gate-screen&#039;);&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        if (loginGateLocked &amp;amp;&amp;amp; requiresLoginGate() &amp;amp;&amp;amp; !options.force) return false;&lt;br /&gt;
        if (!node) {&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        node.classList.add(&#039;is-complete&#039;);&lt;br /&gt;
        node.classList.remove(&#039;is-active&#039;);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
            if (bootNode === node) bootNode = null;&lt;br /&gt;
            loginGateActionNode = null;&lt;br /&gt;
            document.documentElement.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
            if (document.body) document.body.classList.remove(&#039;boot-gate-active&#039;);&lt;br /&gt;
        }, 240);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromNationPayload(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            if (typeof value === &#039;string&#039;) {&lt;br /&gt;
                files.push(value);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof value === &#039;object&#039;) files.push(value.file || value.flag_file || value.flag || value.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function scanItem(item) {&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            if (Array.isArray(item.flags)) item.flags.forEach(add);&lt;br /&gt;
            add(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        (payload &amp;amp;&amp;amp; payload.continents || []).forEach(function (continent) {&lt;br /&gt;
            (continent.regions || []).forEach(function (region) {&lt;br /&gt;
                (region.items || []).forEach(scanItem);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function collectFlagsFromLinkMap(payload) {&lt;br /&gt;
        var files = [];&lt;br /&gt;
        var source = payload &amp;amp;&amp;amp; payload.items ? payload.items : {};&lt;br /&gt;
        Object.keys(source || {}).forEach(function (key) {&lt;br /&gt;
            var item = source[key];&lt;br /&gt;
            if (!item || typeof item !== &#039;object&#039;) return;&lt;br /&gt;
            files.push(item.flag_file || item.flag || item.flag_title || &#039;&#039;);&lt;br /&gt;
        });&lt;br /&gt;
        return unique(files);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prewarmImages(urls, limit) {&lt;br /&gt;
        return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
            limit: Number(limit) &amp;gt; 0 ? Number(limit) : 0,&lt;br /&gt;
            concurrency: 16,&lt;br /&gt;
            persistent: false&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationRuntime() {&lt;br /&gt;
        return window.Decorations || window.CLBI_DECORATIONS || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationRuntime() {&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            var tries = 0;&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var runtime = getDecorationRuntime();&lt;br /&gt;
                if (runtime) return resolve(runtime);&lt;br /&gt;
                tries += 1;&lt;br /&gt;
                if (tries &amp;gt; 40) return resolve(null);&lt;br /&gt;
                window.setTimeout(tick, 25);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesDecorationEntry(entry, filter) {&lt;br /&gt;
        var era = String(filter &amp;amp;&amp;amp; filter.era || &#039;&#039;).trim();&lt;br /&gt;
        var page = String(filter &amp;amp;&amp;amp; filter.page || &#039;&#039;).trim();&lt;br /&gt;
        var entryPage = String(entry &amp;amp;&amp;amp; entry.page || &#039;&#039;).replace(/_/g, &#039; &#039;).trim();&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039;) return false;&lt;br /&gt;
        if (page &amp;amp;&amp;amp; entryPage &amp;amp;&amp;amp; entryPage !== page) return false;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() &amp;amp;&amp;amp; String(entry.era || &#039;&#039;).trim() !== era) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDecorationSet(task, level) {&lt;br /&gt;
        var ref = task.ref || &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
        return window.EntryStore.fetchJsonRef(ref, { noStore: !!task.noStore }).then(function (registry) {&lt;br /&gt;
            var list = registry &amp;amp;&amp;amp; Array.isArray(registry.decorations) ? registry.decorations : [];&lt;br /&gt;
            var pixelRefs = [];&lt;br /&gt;
            list.forEach(function (entry) {&lt;br /&gt;
                var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).toLowerCase();&lt;br /&gt;
                var asset = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
                if (!asset) return;&lt;br /&gt;
                if (type !== &#039;pixel-json&#039; &amp;amp;&amp;amp; !/\.json(?:[?#].*)?$/i.test(asset)) return;&lt;br /&gt;
                if (!matchesDecorationEntry(entry, task)) return;&lt;br /&gt;
                pixelRefs.push(asset);&lt;br /&gt;
            });&lt;br /&gt;
            if (level !== &#039;full&#039; || task.preparePixels === false || !pixelRefs.length) return registry;&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (!runtime || typeof runtime.preparePixelCanvas !== &#039;function&#039;) {&lt;br /&gt;
                    return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                        return window.EntryStore.fetchJsonRef(pixelRef).catch(function () { return null; });&lt;br /&gt;
                    })).then(function () { return registry; });&lt;br /&gt;
                }&lt;br /&gt;
                return Promise.all(pixelRefs.map(function (pixelRef) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(pixelRef).catch(function () { return null; });&lt;br /&gt;
                })).then(function () { return registry; });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareNationsEra(task, level) {&lt;br /&gt;
        var era = String(task.era || &#039;1950&#039;);&lt;br /&gt;
        var listRef = task.listRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_List.json&#039;);&lt;br /&gt;
        var linkRef = task.linkMapRef || (&#039;MediaWiki:&#039; + era + &#039;_Nation_Link_Map.json&#039;);&lt;br /&gt;
        var jsonPhaseId = BootPerf.start(&#039;nations-era json fetch&#039;, { era: era, level: level, listRef: listRef, linkRef: linkRef });&lt;br /&gt;
        var listPromise = window.EntryStore.fetchJsonRef(listRef).catch(function () { return null; });&lt;br /&gt;
        var linkPromise = window.EntryStore.fetchJsonRef(linkRef).catch(function () { return null; });&lt;br /&gt;
&lt;br /&gt;
        return Promise.all([listPromise, linkPromise]).then(function (results) {&lt;br /&gt;
            BootPerf.end(jsonPhaseId, { ok: true });&lt;br /&gt;
            var files;&lt;br /&gt;
            var flagUrls;&lt;br /&gt;
            if (level !== &#039;full&#039; &amp;amp;&amp;amp; level !== &#039;warm&#039;) return results;&lt;br /&gt;
            files = unique(collectFlagsFromNationPayload(results[0]).concat(collectFlagsFromLinkMap(results[1])));&lt;br /&gt;
            return BootPerf.measure(&#039;nations-era flag url resolve&#039;, { era: era, count: files.length }, function () {&lt;br /&gt;
                return window.EntryStore.resolveFlagUrls(files);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                if (level === &#039;warm&#039;) {&lt;br /&gt;
                    /*&lt;br /&gt;
                     * Warm tab contract — 20260708.&lt;br /&gt;
                     *&lt;br /&gt;
                     * A previous tab already stored the same-revision flag URL and image&lt;br /&gt;
                     * blobs.  Re-decoding every flag here makes the second tab feel as slow&lt;br /&gt;
                     * as the first one.  In warm mode we only hydrate the flag URL table from&lt;br /&gt;
                     * EntryCache so NationsPanel can render stable URLs immediately; image&lt;br /&gt;
                     * decode remains demand-driven by the live panel instead of blocking the&lt;br /&gt;
                     * boot screen.&lt;br /&gt;
                     */&lt;br /&gt;
                    return results;&lt;br /&gt;
                }&lt;br /&gt;
                flagUrls = files.map(function (file) { return window.EntryStore.getFlagUrl(file); }).filter(Boolean);&lt;br /&gt;
                /*&lt;br /&gt;
                 * 20260710: Flag URLs are part of the entry contract, but decoding every&lt;br /&gt;
                 * flag image is not.  The 1950 list has more than 150 flags, and waiting&lt;br /&gt;
                 * for all image decodes kept the public boot gate open for several seconds.&lt;br /&gt;
                 * Keep revision-aware URLs ready for the panel, then let the browser load&lt;br /&gt;
                 * the actual images from the live DOM.  A low-priority background warm-up&lt;br /&gt;
                 * may run after the boot gate has opened, so it never competes with the&lt;br /&gt;
                 * current entry surface or the globe ready contract.&lt;br /&gt;
                 */&lt;br /&gt;
                BootPerf.mark(&#039;nations-era flag image prewarm skipped&#039;, {&lt;br /&gt;
                    era: era,&lt;br /&gt;
                    count: flagUrls.length,&lt;br /&gt;
                    limit: task.flagLimit == null ? 0 : Number(task.flagLimit),&lt;br /&gt;
                    reason: &#039;deferred-after-boot-gate&#039;&lt;br /&gt;
                });&lt;br /&gt;
                if (flagUrls.length &amp;amp;&amp;amp; task.deferFlagImages !== false) {&lt;br /&gt;
                    deferredEntryWarmups.push(function () {&lt;br /&gt;
                        return BootPerf.measure(&#039;deferred nations-era flag image prewarm&#039;, {&lt;br /&gt;
                            era: era,&lt;br /&gt;
                            count: flagUrls.length,&lt;br /&gt;
                            limit: task.flagLimit == null ? 0 : Number(task.flagLimit)&lt;br /&gt;
                        }, function () {&lt;br /&gt;
                            return prewarmImages(flagUrls, task.flagLimit == null ? 0 : Number(task.flagLimit));&lt;br /&gt;
                        });&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                return results;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareGlobeSharedAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;globe-shared url resolve&#039;, { count: refs.length, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            /*&lt;br /&gt;
             * Globe shared assets are heavy 6K texture sources.  The live&lt;br /&gt;
             * NationsGlobe ready contract already waits for the actual Three.js&lt;br /&gt;
             * texture path, so decoding the same images here only duplicates work&lt;br /&gt;
             * and holds the boot screen.  Keep revision-aware URL resolution in the&lt;br /&gt;
             * entry ledger, but make the image warm-up non-blocking.&lt;br /&gt;
             */&lt;br /&gt;
            BootPerf.mark(&#039;globe-shared image prewarm skipped&#039;, {&lt;br /&gt;
                count: urls.length,&lt;br /&gt;
                level: level,&lt;br /&gt;
                reason: &#039;deferred-to-nations-globe-ready-contract&#039;&lt;br /&gt;
            });&lt;br /&gt;
            window.setTimeout(function () {&lt;br /&gt;
                if (!window.EntryStore || typeof window.EntryStore.preloadImages !== &#039;function&#039;) return;&lt;br /&gt;
                window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 4)),&lt;br /&gt;
                    persistent: false&lt;br /&gt;
                }).catch(function () {});&lt;br /&gt;
            }, 0);&lt;br /&gt;
            return urls;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareImageAssets(task, level) {&lt;br /&gt;
        var refs = Array.isArray(task &amp;amp;&amp;amp; task.assets) ? task.assets : [];&lt;br /&gt;
        var full = level === &#039;full&#039;;&lt;br /&gt;
        var exposeAs = String(task &amp;amp;&amp;amp; task.exposeAs || &#039;&#039;).trim();&lt;br /&gt;
        if (!refs.length) return Promise.resolve(null);&lt;br /&gt;
        return BootPerf.measure(&#039;image-assets url resolve&#039;, { count: refs.length, exposeAs: exposeAs, level: level }, function () {&lt;br /&gt;
            return Promise.all(refs.map(function (ref) {&lt;br /&gt;
                return window.EntryStore.resolveFileUrl(ref).catch(function () { return &#039;&#039;; });&lt;br /&gt;
            }));&lt;br /&gt;
        }).then(function (urls) {&lt;br /&gt;
            urls = urls.filter(Boolean);&lt;br /&gt;
            if (exposeAs === &#039;nationsGlobeLoadingGif&#039;) {&lt;br /&gt;
                window.NationsGlobeLoadingGifRef = refs[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifUrl = urls[0] || &#039;&#039;;&lt;br /&gt;
                window.NationsGlobeLoadingGifFile = &#039;Gfx-vhs-glitch-001.gif&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (!full) return urls;&lt;br /&gt;
            return BootPerf.measure(&#039;image-assets image prewarm&#039;, { count: urls.length, exposeAs: exposeAs, limit: task.imageLimit == null ? 0 : Number(task.imageLimit), concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2)) }, function () {&lt;br /&gt;
                return window.EntryStore.preloadImages(urls, {&lt;br /&gt;
                    limit: task.imageLimit == null ? 0 : Number(task.imageLimit),&lt;br /&gt;
                    concurrency: Math.max(1, Math.min(16, Number(task.concurrency) || 2))&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () { return urls; });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareHtmlEntry(task) {&lt;br /&gt;
        var url = String(task &amp;amp;&amp;amp; (task.url || task.ref) || &#039;&#039;).trim();&lt;br /&gt;
        if (!url) return Promise.resolve(null);&lt;br /&gt;
        return window.EntryStore.fetchTextUrl(url, { noStore: !!task.noStore, resourceRef: task.resourceRef || task.ref || task.title || &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareTask(task, defaultLevel) {&lt;br /&gt;
        var level = String(task &amp;amp;&amp;amp; (task.level || defaultLevel) || &#039;half&#039;).toLowerCase();&lt;br /&gt;
        var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
        if (!task || typeof task !== &#039;object&#039;) return Promise.resolve(null);&lt;br /&gt;
        if (type === &#039;html&#039;) return prepareHtmlEntry(task);&lt;br /&gt;
        if (type === &#039;json&#039;) return window.EntryStore.fetchJsonRef(task.ref, { noStore: !!task.noStore });&lt;br /&gt;
        if (type === &#039;pixel-json&#039;) {&lt;br /&gt;
            return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
                if (level === &#039;full&#039; &amp;amp;&amp;amp; runtime &amp;amp;&amp;amp; typeof runtime.preparePixelCanvas === &#039;function&#039;) {&lt;br /&gt;
                    return runtime.preparePixelCanvas(task.ref || task.asset);&lt;br /&gt;
                }&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(task.ref || task.asset);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        if (type === &#039;decorations&#039;) return prepareDecorationSet(task, level);&lt;br /&gt;
        if (type === &#039;nations-era&#039;) return prepareNationsEra(task, level);&lt;br /&gt;
        if (type === &#039;globe-shared-assets&#039;) return prepareGlobeSharedAssets(task, level);&lt;br /&gt;
        if (type === &#039;image-assets&#039;) return prepareImageAssets(task, level);&lt;br /&gt;
        return Promise.resolve(null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadManifest() {&lt;br /&gt;
        return BootPerf.measure(&#039;revision manifest load&#039;, {}, function () {&lt;br /&gt;
            return (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.load === &#039;function&#039; ? window.RevisionManifest.load() : Promise.resolve(null));&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;entry manifest json fetch&#039;, { ref: MANIFEST_TITLE }, function () {&lt;br /&gt;
                return window.EntryStore.fetchJsonRef(MANIFEST_TITLE, { noStore: false });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (manifest) {&lt;br /&gt;
            if (!manifest || typeof manifest !== &#039;object&#039; || !manifest.version) {&lt;br /&gt;
                BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: &#039;invalid manifest&#039; });&lt;br /&gt;
                return defaultManifest;&lt;br /&gt;
            }&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest ready&#039;, { version: manifest.version });&lt;br /&gt;
            return manifest;&lt;br /&gt;
        }).catch(function (err) {&lt;br /&gt;
            BootPerf.mark(&#039;entry manifest fallback&#039;, { reason: err &amp;amp;&amp;amp; (err.message || String(err)) || &#039;load failed&#039; });&lt;br /&gt;
            return defaultManifest;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function flattenInitialTasks(manifest) {&lt;br /&gt;
        var initial = manifest &amp;amp;&amp;amp; manifest.initial ? manifest.initial : {};&lt;br /&gt;
        var full = Array.isArray(initial.full) ? initial.full : [];&lt;br /&gt;
        var half = Array.isArray(initial.half) ? initial.half : [];&lt;br /&gt;
        var tasks = [];&lt;br /&gt;
        full.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;full&#039;;&lt;br /&gt;
            task.blocking = true;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        half.forEach(function (task) {&lt;br /&gt;
            task = Object.assign({}, task);&lt;br /&gt;
            task.level = task.level || &#039;half&#039;;&lt;br /&gt;
            task.blocking = false;&lt;br /&gt;
            tasks.push(task);&lt;br /&gt;
        });&lt;br /&gt;
        return tasks;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function flattenWarmInitialTasks(tasks) {&lt;br /&gt;
        var warmed = [];&lt;br /&gt;
        (tasks || []).forEach(function (task) {&lt;br /&gt;
            var copy;&lt;br /&gt;
            var type = String(task &amp;amp;&amp;amp; task.type || &#039;&#039;).toLowerCase();&lt;br /&gt;
            if (!task || task.blocking === false) return;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Warm boot fast path — 20260708.&lt;br /&gt;
             *&lt;br /&gt;
             * Cold boot intentionally performs the expensive work: resolving every flag,&lt;br /&gt;
             * downloading/decoding images, preparing decoration canvases, and warming shared&lt;br /&gt;
             * globe textures.  Once a tab has certified the pack against the current revision&lt;br /&gt;
             * manifest, later tabs must not replay that full workload.  They only hydrate the&lt;br /&gt;
             * small tables needed by the live page and let already-cached image/blob data be&lt;br /&gt;
             * consumed on demand.  This is the missing layer that made second tabs feel almost&lt;br /&gt;
             * as slow as first tabs even though the string checks were true.&lt;br /&gt;
             */&lt;br /&gt;
            if (type === &#039;globe-shared-assets&#039;) return;&lt;br /&gt;
            if (type === &#039;image-assets&#039;) return;&lt;br /&gt;
            copy = Object.assign({}, task);&lt;br /&gt;
            if (type === &#039;nations-era&#039;) copy.level = &#039;warm&#039;;&lt;br /&gt;
            else if (type === &#039;decorations&#039;) {&lt;br /&gt;
                copy.level = &#039;half&#039;;&lt;br /&gt;
                copy.preparePixels = false;&lt;br /&gt;
            } else if (type === &#039;pixel-json&#039;) copy.level = &#039;half&#039;;&lt;br /&gt;
            warmed.push(copy);&lt;br /&gt;
        });&lt;br /&gt;
        return warmed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackKey(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        return &#039;initial-entry-full:&#039; + version;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentInitialPackToken(manifest) {&lt;br /&gt;
        var version = manifest &amp;amp;&amp;amp; manifest.version ? String(manifest.version) : BUILD_ID;&lt;br /&gt;
        var revToken = window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.manifestToken === &#039;function&#039; ? window.RevisionManifest.manifestToken(version) : version;&lt;br /&gt;
        return revToken;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isInitialPackWarm(manifest) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        return !!(window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.packReady === &#039;function&#039; &amp;amp;&amp;amp; window.EntryCache.packReady(key, token));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function markInitialPackWarm(manifest, meta) {&lt;br /&gt;
        var key = currentInitialPackKey(manifest);&lt;br /&gt;
        var token = currentInitialPackToken(manifest);&lt;br /&gt;
        if (window.EntryCache &amp;amp;&amp;amp; typeof window.EntryCache.setPackReady === &#039;function&#039;) {&lt;br /&gt;
            window.EntryCache.setPackReady(key, token, Object.assign({ manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || BUILD_ID }, meta || {}));&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function waitMs(ms) {&lt;br /&gt;
        return new Promise(function (resolve) { window.setTimeout(resolve, Math.max(0, ms || 0)); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForBody() {&lt;br /&gt;
        if (document.body) return Promise.resolve(document.body);&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                if (document.body) return resolve(document.body);&lt;br /&gt;
                window.setTimeout(tick, 10);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDomReady() {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) return Promise.resolve();&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;, resolve, { once: true });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForAnimationFrames(count) {&lt;br /&gt;
        count = Math.max(1, Math.round(count || 1));&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function next(left) {&lt;br /&gt;
                if (left &amp;lt;= 0) return resolve();&lt;br /&gt;
                window.requestAnimationFrame(function () { next(left - 1); });&lt;br /&gt;
            }&lt;br /&gt;
            next(count);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitUntil(predicate, options) {&lt;br /&gt;
        var started = now();&lt;br /&gt;
        var timeout = options &amp;amp;&amp;amp; options.timeoutMs ? options.timeoutMs : 8000;&lt;br /&gt;
        var interval = options &amp;amp;&amp;amp; options.intervalMs ? options.intervalMs : 60;&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function tick() {&lt;br /&gt;
                var result = false;&lt;br /&gt;
                try { result = predicate(); } catch (err) { result = false; }&lt;br /&gt;
                if (result) return resolve({ ok: true, value: result });&lt;br /&gt;
                if (now() - started &amp;gt;= timeout) return resolve({ ok: false, timeout: true });&lt;br /&gt;
                window.setTimeout(tick, interval);&lt;br /&gt;
            }&lt;br /&gt;
            tick();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForTrackedScripts() {&lt;br /&gt;
        var list = (window.EntryScriptLoads || []).slice();&lt;br /&gt;
        if (!list.length) return Promise.resolve([]);&lt;br /&gt;
        return Promise.all(list.map(function (promise) {&lt;br /&gt;
            return Promise.resolve(promise).catch(function (err) { return { ok: false, error: err }; });&lt;br /&gt;
        }));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDocumentSurface() {&lt;br /&gt;
        return waitForDomReady().then(function () {&lt;br /&gt;
            return waitForBody();&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                return document.querySelector(&#039;.content-wrapper&#039;) &amp;amp;&amp;amp; document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
            }, { timeoutMs: 8000, intervalMs: 50 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isNationsPageSurface() {&lt;br /&gt;
        var page = String(mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
        return !!document.querySelector(&#039;.clbi-nations-panel-stack&#039;) || /(?:^|[_ ])시대(?:$|[_ ])/.test(page) || /(?:^|[_ ])Era(?:$|[_ ])/i.test(page);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getInitialNationsEra(manifest) {&lt;br /&gt;
        var tasks = flattenInitialTasks(manifest);&lt;br /&gt;
        var i;&lt;br /&gt;
        for (i = 0; i &amp;lt; tasks.length; i += 1) {&lt;br /&gt;
            if (tasks[i] &amp;amp;&amp;amp; tasks[i].type === &#039;nations-era&#039; &amp;amp;&amp;amp; String(tasks[i].level || &#039;&#039;).toLowerCase() === &#039;full&#039;) {&lt;br /&gt;
                return String(tasks[i].era || &#039;1950&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;1950&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsPanelReady(era) {&lt;br /&gt;
        return BootPerf.measure(&#039;wait nations panel api&#039;, { era: era }, function () {&lt;br /&gt;
            return waitUntil(function () { return window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.whenEraReady === &#039;function&#039;; }, {&lt;br /&gt;
                timeoutMs: 8000,&lt;br /&gt;
                intervalMs: 50&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function (result) {&lt;br /&gt;
            if (!result.ok || !window.NationsPanel || typeof window.NationsPanel.whenEraReady !== &#039;function&#039;) return null;&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel era ready&#039;, { era: era }, function () {&lt;br /&gt;
                return window.NationsPanel.whenEraReady(era, { timeoutMs: 15000 }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            return BootPerf.measure(&#039;wait nations panel dom ready&#039;, { era: era }, function () {&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var panel = document.querySelector(&#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + era + &#039;&amp;quot;] .clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;) ||&lt;br /&gt;
                        document.querySelector(&#039;.clbi-nations-tabpanel[data-nation-list-source=&amp;quot;1&amp;quot;]&#039;);&lt;br /&gt;
                    return panel &amp;amp;&amp;amp; panel.classList.contains(&#039;clbi-nations-list-json-ready&#039;) &amp;amp;&amp;amp; panel.getAttribute(&#039;data-nation-list-year-loaded&#039;) === String(era);&lt;br /&gt;
                }, { timeoutMs: 15000, intervalMs: 80 });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForNationsGlobeReady(options) {&lt;br /&gt;
        var globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
        var soft;&lt;br /&gt;
        var timeout;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        soft = !!options.soft;&lt;br /&gt;
        timeout = Number(options.timeoutMs || (soft ? 450 : 30000));&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            BootPerf.mark(&#039;wait nations globe skipped&#039;, { reason: &#039;no globe node&#039;, soft: soft });&lt;br /&gt;
            return Promise.resolve(null);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return BootPerf.measure(soft ? &#039;wait nations globe warm attach&#039; : &#039;wait nations globe ready contract&#039;, { soft: soft, timeoutMs: timeout }, function () {&lt;br /&gt;
            if (soft) {&lt;br /&gt;
                /*&lt;br /&gt;
                Warm-tab policy:&lt;br /&gt;
                Raw globe images are already revision-checked and blob-cached by EntryCache,&lt;br /&gt;
                but WebGL scene creation, GPU upload, topojson parsing, and per-tab Three.js&lt;br /&gt;
                objects cannot be shared across browser tabs.  Waiting for the full is-ready&lt;br /&gt;
                contract here makes a warm tab slower than the first tab.  In warm mode the&lt;br /&gt;
                initial gate only waits until the globe consumer has been attached, then the&lt;br /&gt;
                globe finishes its own per-tab hydrate without holding the whole page hostage.&lt;br /&gt;
                */&lt;br /&gt;
                return waitUntil(function () {&lt;br /&gt;
                    var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                    if (!node) return false;&lt;br /&gt;
                    return node.getAttribute(&#039;data-nations-globe-ready&#039;) === &#039;1&#039; ||&lt;br /&gt;
                        !!node.CLBI_NationsGlobeInstance ||&lt;br /&gt;
                        !!node.querySelector(&#039;.clbi-nations-globe-stage&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;is-ready&#039;) ||&lt;br /&gt;
                        node.classList.contains(&#039;has-error&#039;);&lt;br /&gt;
                }, { timeoutMs: timeout, intervalMs: 40 });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return waitUntil(function () {&lt;br /&gt;
                var node = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
                return node &amp;amp;&amp;amp; (node.classList.contains(&#039;is-ready&#039;) || node.classList.contains(&#039;has-error&#039;));&lt;br /&gt;
            }, { timeoutMs: timeout, intervalMs: 100 });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForDecorationsReady(options) {&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        return waitForDecorationRuntime().then(function (runtime) {&lt;br /&gt;
            if (!runtime) return null;&lt;br /&gt;
            if (options.warm) {&lt;br /&gt;
                /*&lt;br /&gt;
                 * Warm tab contract — 20260708.&lt;br /&gt;
                 *&lt;br /&gt;
                 * The decoration registry and pixel canvases were prepared by the cold tab.&lt;br /&gt;
                 * Calling reload() here re-reads and re-syncs the decoration layer during the&lt;br /&gt;
                 * boot gate, which is visible as a pointless delay.  A warm tab only needs the&lt;br /&gt;
                 * current visibility pass so boot can release quickly and editing/preview tools&lt;br /&gt;
                 * remain responsive.&lt;br /&gt;
                 */&lt;br /&gt;
                try {&lt;br /&gt;
                    if (typeof runtime.updateVisibility === &#039;function&#039;) runtime.updateVisibility(document);&lt;br /&gt;
                    else if (typeof runtime.sync === &#039;function&#039;) runtime.sync();&lt;br /&gt;
                } catch (err) {}&lt;br /&gt;
                return waitForAnimationFrames(1);&lt;br /&gt;
            }&lt;br /&gt;
            if (typeof runtime.reload !== &#039;function&#039;) return null;&lt;br /&gt;
            return runtime.reload().catch(function () { return null; }).then(function () {&lt;br /&gt;
                return waitForAnimationFrames(2);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForCurrentEntrySurface(manifest, options) {&lt;br /&gt;
        var era = getInitialNationsEra(manifest);&lt;br /&gt;
        var warmPack;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        warmPack = !!options.warmPack;&lt;br /&gt;
&lt;br /&gt;
        if (!isNationsPageSurface()) {&lt;br /&gt;
            return waitForDecorationsReady({ warm: !!warmPack }).then(function () { return waitForAnimationFrames(1); });&lt;br /&gt;
        }&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = warmPack ? &#039;Hydrating current information surface from warm cache&#039; : &#039;Preparing current information surface&#039;;&lt;br /&gt;
        if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for era &#039; + era + &#039; ready contract&#039;;&lt;br /&gt;
        return waitForNationsPanelReady(era)&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (warmPack) {&lt;br /&gt;
                    if (bootDetailNode) bootDetailNode.textContent = &#039;attaching globe warm consumer&#039;;&lt;br /&gt;
                    return waitForNationsGlobeReady({ soft: true, timeoutMs: 450 });&lt;br /&gt;
                }&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for globe ready contract&#039;;&lt;br /&gt;
                return waitForNationsGlobeReady();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () {&lt;br /&gt;
                if (bootDetailNode) bootDetailNode.textContent = &#039;waiting for decoration surface&#039;;&lt;br /&gt;
                return waitForDecorationsReady({ warm: warmPack });&lt;br /&gt;
            })&lt;br /&gt;
            .then(function () { return waitForAnimationFrames(warmPack ? 1 : 2); });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runDeferredEntryWarmups() {&lt;br /&gt;
        var queue = deferredEntryWarmups.splice(0);&lt;br /&gt;
        if (!queue.length) return;&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            var chain = Promise.resolve();&lt;br /&gt;
            queue.forEach(function (job) {&lt;br /&gt;
                chain = chain.then(function () {&lt;br /&gt;
                    try { return job(); }&lt;br /&gt;
                    catch (err) { return null; }&lt;br /&gt;
                }).catch(function () { return null; });&lt;br /&gt;
            });&lt;br /&gt;
        }, 1200);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function runInitialLoad(options) {&lt;br /&gt;
        var done = 0;&lt;br /&gt;
        var manifestRef;&lt;br /&gt;
        var tasks;&lt;br /&gt;
        var bootOptions;&lt;br /&gt;
        var timedOut = false;&lt;br /&gt;
        var warmPack = false;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        bootStartTime = window.__BootGatePrelude &amp;amp;&amp;amp; window.__BootGatePrelude.startTime ? window.__BootGatePrelude.startTime : now();&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        ensureBootScreen();&lt;br /&gt;
        updateBootProgress(0, 1, &#039;loading entry manifest&#039;);&lt;br /&gt;
&lt;br /&gt;
        manifestRef = loadManifest().then(function (manifest) {&lt;br /&gt;
            var minDisplay;&lt;br /&gt;
            var totalSteps;&lt;br /&gt;
            var maxBlockingMs;&lt;br /&gt;
            var timeoutHandle;&lt;br /&gt;
            bootOptions = manifest.boot || {};&lt;br /&gt;
            warmPack = isInitialPackWarm(manifest);&lt;br /&gt;
            minDisplay = options.minDisplayMs || (warmPack ? (bootOptions.cachedMinDisplayMs || 220) : (bootOptions.minDisplayMs || 950));&lt;br /&gt;
            tasks = flattenInitialTasks(manifest);&lt;br /&gt;
            if (!tasks.length) tasks = flattenInitialTasks(defaultManifest);&lt;br /&gt;
            if (warmPack) tasks = flattenWarmInitialTasks(tasks);&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            Full/half contract:&lt;br /&gt;
            These manifest tasks only prepare data assets.  The gate is not allowed to open&lt;br /&gt;
            until the current page surface has consumed that data and reported a real ready&lt;br /&gt;
            state below.  Do not reintroduce localStorage-only skip logic here; a cached&lt;br /&gt;
            version number is not the same as current-tab readiness.&lt;br /&gt;
            */&lt;br /&gt;
            totalSteps = tasks.length + 4;&lt;br /&gt;
            updateBootProgress(0, totalSteps, warmPack ? &#039;hydrating warm entry cache&#039; : &#039;starting entry packs&#039;);&lt;br /&gt;
&lt;br /&gt;
            return new Promise(function (resolve) {&lt;br /&gt;
                maxBlockingMs = Number(options.maxBlockingMs || bootOptions.maxBlockingMs || 30000);&lt;br /&gt;
                timeoutHandle = window.setTimeout(function () {&lt;br /&gt;
                    timedOut = true;&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }, maxBlockingMs);&lt;br /&gt;
&lt;br /&gt;
                Promise.all(tasks.map(function (task) {&lt;br /&gt;
                    var label = task.label || task.id || task.type || &#039;entry task&#039;;&lt;br /&gt;
                    return BootPerf.measure(&#039;boot task: &#039; + label, { id: task.id || &#039;&#039;, type: task.type || &#039;&#039;, level: task.level || task.level === 0 ? task.level : &#039;&#039; }, function () {&lt;br /&gt;
                        return prepareTask(task, task.level);&lt;br /&gt;
                    }).catch(function () {&lt;br /&gt;
                        return null;&lt;br /&gt;
                    }).then(function () {&lt;br /&gt;
                        done += 1;&lt;br /&gt;
                        updateBootProgress(done, totalSteps, label);&lt;br /&gt;
                    });&lt;br /&gt;
                })).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;loading subsystem scripts&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait tracked subsystem scripts&#039;, { count: (window.EntryScriptLoads || []).length }, function () { return waitForTrackedScripts(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for document shell&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait document shell&#039;, {}, function () { return waitForDocumentSurface(); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(++done, totalSteps, &#039;waiting for current entry surface&#039;);&lt;br /&gt;
                    return BootPerf.measure(&#039;wait current entry surface&#039;, { warmPack: warmPack }, function () { return waitForCurrentEntrySurface(manifest, { warmPack: warmPack }); });&lt;br /&gt;
                }).then(function () {&lt;br /&gt;
                    updateBootProgress(totalSteps, totalSteps, warmPack ? &#039;warm entry surface complete&#039; : &#039;entry surface complete&#039;);&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                }).catch(function () {&lt;br /&gt;
                    window.clearTimeout(timeoutHandle);&lt;br /&gt;
                    resolve();&lt;br /&gt;
                });&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                var elapsed = now() - bootStartTime;&lt;br /&gt;
                var wait = Math.max(0, minDisplay - elapsed);&lt;br /&gt;
                return waitMs(wait);&lt;br /&gt;
            }).then(function () {&lt;br /&gt;
                localStorage.setItem(READY_KEY, String(manifest.version));&lt;br /&gt;
                if (!timedOut) markInitialPackWarm(manifest, { warmSource: warmPack ? &#039;cache-hit&#039; : &#039;cold-build&#039; });&lt;br /&gt;
                if (bootStatusNode) bootStatusNode.textContent = timedOut ? &#039;Entry surface ready with deferred items&#039; : (warmPack ? &#039;Entry surface ready from warm cache&#039; : &#039;Entry surface ready&#039;);&lt;br /&gt;
                updateBootProgress(tasks.length + 4, tasks.length + 4, timedOut ? &#039;timeout: continuing deferred loading&#039; : (warmPack ? &#039;warm cache complete&#039; : &#039;complete&#039;));&lt;br /&gt;
                BootPerf.mark(&#039;boot gate complete&#039;, { timedOut: timedOut, warmPack: warmPack, manifestVersion: manifest &amp;amp;&amp;amp; manifest.version || &#039;&#039; });&lt;br /&gt;
                BootPerf.print();&lt;br /&gt;
                runDeferredEntryWarmups();&lt;br /&gt;
                window.setTimeout(function () {&lt;br /&gt;
                    if (requiresLoginGate()) showLoginGate();&lt;br /&gt;
                    else hideBootScreen();&lt;br /&gt;
                }, 120);&lt;br /&gt;
                return manifest;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return manifestRef;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startBoot(options) {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return Promise.resolve({ skipped: true, reason: &#039;developer-or-editing-page&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        if (hasBootParam(&#039;0&#039;) &amp;amp;&amp;amp; !requiresLoginGate() &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return Promise.resolve(null);&lt;br /&gt;
        if (bootStarted &amp;amp;&amp;amp; bootPromise &amp;amp;&amp;amp; !(options &amp;amp;&amp;amp; options.force)) return bootPromise;&lt;br /&gt;
        bootStarted = true;&lt;br /&gt;
        bootPromise = runInitialLoad(options || {});&lt;br /&gt;
        return bootPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resetBoot() {&lt;br /&gt;
        localStorage.removeItem(READY_KEY);&lt;br /&gt;
        bootStarted = false;&lt;br /&gt;
        bootPromise = null;&lt;br /&gt;
        loginGateLocked = false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    BootGate intentionally has no SPA hold/release API.&lt;br /&gt;
    Initial boot is the only blocking phase; later SPA routes must consume prepared&lt;br /&gt;
    EntryStore artifacts without reopening the loading surface.&lt;br /&gt;
    */&lt;br /&gt;
&lt;br /&gt;
    function buildBootReport() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: BUILD_ID,&lt;br /&gt;
            state: {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                excludedPage: BOOT_EXCLUDED_PAGE,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;))&lt;br /&gt;
            },&lt;br /&gt;
            store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
            bootPerf: window.BootPerf &amp;amp;&amp;amp; typeof window.BootPerf.summary === &#039;function&#039; ? window.BootPerf.summary() : null,&lt;br /&gt;
            revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null,&lt;br /&gt;
            nations: window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.diagnostics === &#039;function&#039; ? window.NationsPanel.diagnostics() : null,&lt;br /&gt;
            decorations: window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.diagnostics === &#039;function&#039; ? window.Decorations.diagnostics() : null&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.EntryLoader = window.EntryLoader || {&lt;br /&gt;
        loadManifest: loadManifest,&lt;br /&gt;
        prepareTask: prepareTask,&lt;br /&gt;
        prepareNationsEra: prepareNationsEra,&lt;br /&gt;
        prepareDecorationSet: prepareDecorationSet,&lt;br /&gt;
        prepareGlobeSharedAssets: prepareGlobeSharedAssets,&lt;br /&gt;
        prepareImageAssets: prepareImageAssets,&lt;br /&gt;
        prepareHtmlEntry: prepareHtmlEntry,&lt;br /&gt;
        runInitialLoad: runInitialLoad&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function showBootPreview(options) {&lt;br /&gt;
        var node;&lt;br /&gt;
        options = options || {};&lt;br /&gt;
        /*&lt;br /&gt;
         * Boot preview is a design/editing surface, not a real boot gate.&lt;br /&gt;
         * The earlier preview reused activateBootSurface(), which applied&lt;br /&gt;
         * html.boot-gate-active and hid the entire wiki shell, including&lt;br /&gt;
         * DevTools.  That made it impossible to edit loading-screen&lt;br /&gt;
         * decorations while previewing them.  Keep the real first-entry gate&lt;br /&gt;
         * full-screen, but make preview a small non-blocking surface below the&lt;br /&gt;
         * DevTools z-index so the owner can keep using the editor.&lt;br /&gt;
         */&lt;br /&gt;
        node = ensureBootScreen({ preview: true });&lt;br /&gt;
        if (!node) return null;&lt;br /&gt;
        node.classList.add(&#039;is-preview&#039;);&lt;br /&gt;
        if (bootStatusNode) bootStatusNode.textContent = options.status || &#039;Loading screen preview&#039;;&lt;br /&gt;
        updateBootProgress(Number(options.progress || 64), 100, options.detail || &#039;preview mode: no entry tasks are running&#039;);&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return node;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.BootGate = window.BootGate || {&lt;br /&gt;
        version: BUILD_ID,&lt;br /&gt;
        start: startBoot,&lt;br /&gt;
        show: function (options) {&lt;br /&gt;
            options = options || {};&lt;br /&gt;
            options.force = true;&lt;br /&gt;
            resetBoot();&lt;br /&gt;
            return startBoot(options);&lt;br /&gt;
        },&lt;br /&gt;
        reset: resetBoot,&lt;br /&gt;
        hide: hideBootScreen,&lt;br /&gt;
        preview: showBootPreview,&lt;br /&gt;
        report: buildBootReport,&lt;br /&gt;
        state: function () {&lt;br /&gt;
            return {&lt;br /&gt;
                started: bootStarted,&lt;br /&gt;
                loginRequired: requiresLoginGate(),&lt;br /&gt;
                loginLocked: loginGateLocked,&lt;br /&gt;
                hasNode: !!((bootNode &amp;amp;&amp;amp; bootNode.parentNode) || document.getElementById(&#039;boot-gate-screen&#039;)),&lt;br /&gt;
                htmlActive: !!(document.documentElement &amp;amp;&amp;amp; document.documentElement.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                bodyActive: !!(document.body &amp;amp;&amp;amp; document.body.classList.contains(&#039;boot-gate-active&#039;)),&lt;br /&gt;
                prelude: !!window.__BootGatePrelude,&lt;br /&gt;
                store: window.EntryStore &amp;amp;&amp;amp; window.EntryStore.cacheInfo ? window.EntryStore.cacheInfo() : null,&lt;br /&gt;
                revisionManifest: window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.status === &#039;function&#039; ? window.RevisionManifest.status() : null&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function prime() {&lt;br /&gt;
        if (BOOT_EXCLUDED_PAGE) {&lt;br /&gt;
            hideBootScreen();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        activateBootSurface();&lt;br /&gt;
        waitForBody().then(function () {&lt;br /&gt;
            ensureBootScreen();&lt;br /&gt;
            return waitForAnimationFrames(1);&lt;br /&gt;
        }).then(function () {&lt;br /&gt;
            startBoot();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    waitForBody().then(function () {&lt;br /&gt;
        if (!BOOT_EXCLUDED_PAGE) ensureBootScreen();&lt;br /&gt;
        else hideBootScreen();&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(prime, 0);&lt;br /&gt;
})(window, document, window.mediaWiki || window.mw);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:DevTools.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsPanel.js&#039;);&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:NationsGlobe.js&#039;);&lt;br /&gt;
&lt;br /&gt;
/* CLBI safety guard: adaptive reset functions must exist before shell metric callbacks run. */&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.resetLeftRecentAdaptiveState = resetLeftRecentAdaptiveState;&lt;br /&gt;
window.resetLeftBillboardAdaptiveState = resetLeftBillboardAdaptiveState;&lt;br /&gt;
&lt;br /&gt;
loadClbiRawScript(&#039;MediaWiki:AnecdoteViewer.js&#039;);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var SYSTEM_TITLE_NAMESPACES = {&lt;br /&gt;
        &#039;-1&#039;: true,&lt;br /&gt;
        &#039;4&#039;: true,&lt;br /&gt;
        &#039;5&#039;: true,&lt;br /&gt;
        &#039;6&#039;: true,&lt;br /&gt;
        &#039;7&#039;: true,&lt;br /&gt;
        &#039;8&#039;: true,&lt;br /&gt;
        &#039;9&#039;: true,&lt;br /&gt;
        &#039;10&#039;: true,&lt;br /&gt;
        &#039;11&#039;: true,&lt;br /&gt;
        &#039;12&#039;: true,&lt;br /&gt;
        &#039;13&#039;: true,&lt;br /&gt;
        &#039;14&#039;: true,&lt;br /&gt;
        &#039;15&#039;: true,&lt;br /&gt;
        &#039;828&#039;: true,&lt;br /&gt;
        &#039;829&#039;: true&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    function normalizePageNameForShell(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function readCurrentPageNameForShell() {&lt;br /&gt;
        var pageName = mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (pageName) {&lt;br /&gt;
            return normalizePageNameForShell(pageName);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return normalizePageNameForShell(window.location.pathname || &#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isAnecdoteNamespaceForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var canonicalNamespace = String(mw.config.get(&#039;wgCanonicalNamespace&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 3000 ||&lt;br /&gt;
            canonicalNamespace === &#039;anecdote&#039; ||&lt;br /&gt;
            /^(anecdote|에넥도트):/i.test(pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isBackendOrSystemPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var action = String(mw.config.get(&#039;wgAction&#039;) || &#039;view&#039;).toLowerCase();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var lowerPageName = pageName.toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === &#039;대문&#039;) {&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (SYSTEM_TITLE_NAMESPACES[String(namespaceNumber)]) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;json&#039; || contentModel === &#039;sanitized-css&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/\.(css|js|json)$/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (/^(mediawiki|미디어위키|special|특수):/i.test(pageName)) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMediaWikiSystemAssetPageForShell() {&lt;br /&gt;
        var namespaceNumber = Number(mw.config.get(&#039;wgNamespaceNumber&#039;));&lt;br /&gt;
        var pageName = readCurrentPageNameForShell();&lt;br /&gt;
        var contentModel = String(mw.config.get(&#039;wgPageContentModel&#039;) || &#039;&#039;).toLowerCase();&lt;br /&gt;
&lt;br /&gt;
        return namespaceNumber === 8 &amp;amp;&amp;amp;&lt;br /&gt;
            (/\.(css|js)$/i.test(pageName) || contentModel === &#039;css&#039; || contentModel === &#039;javascript&#039; || contentModel === &#039;sanitized-css&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    var systemDocRawFetchToken = 0;&lt;br /&gt;
&lt;br /&gt;
    function cleanupLegacySystemDocCodeMutationsForShell() {&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codepane&#039;).forEach(function (pane) {&lt;br /&gt;
            var parent;&lt;br /&gt;
&lt;br /&gt;
            if (!pane || !pane.parentNode) return;&lt;br /&gt;
&lt;br /&gt;
            parent = pane.parentNode;&lt;br /&gt;
            while (pane.firstChild) {&lt;br /&gt;
                parent.insertBefore(pane.firstChild, pane);&lt;br /&gt;
            }&lt;br /&gt;
            parent.removeChild(pane);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-doc-codebox&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-doc-codebox&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;style&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocOutputForShell() {&lt;br /&gt;
        return document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findSystemDocSourceNodeForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var children;&lt;br /&gt;
        var preferred;&lt;br /&gt;
&lt;br /&gt;
        if (!output) return null;&lt;br /&gt;
&lt;br /&gt;
        children = Array.prototype.slice.call(output.children || [])&lt;br /&gt;
            .filter(function (el) {&lt;br /&gt;
                return el &amp;amp;&amp;amp; el.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-doc-indicator-row&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    el.id !== &#039;clbi-system-source-viewer&#039; &amp;amp;&amp;amp;&lt;br /&gt;
                    !el.classList.contains(&#039;catlinks&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    (el.textContent || &#039;&#039;).trim().length &amp;gt; 200;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
        preferred = children.filter(function (el) {&lt;br /&gt;
            return el.matches &amp;amp;&amp;amp; el.matches(&#039;.mw-highlight, .mw-code, pre&#039;);&lt;br /&gt;
        })[0];&lt;br /&gt;
&lt;br /&gt;
        return preferred || children.sort(function (a, b) {&lt;br /&gt;
            return (b.textContent || &#039;&#039;).trim().length - (a.textContent || &#039;&#039;).trim().length;&lt;br /&gt;
        })[0] || null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getSystemDocRawUrlForShell() {&lt;br /&gt;
        var title = mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell();&lt;br /&gt;
        var url;&lt;br /&gt;
&lt;br /&gt;
        if (window.mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            url = mw.util.getUrl(title, {&lt;br /&gt;
                action: &#039;raw&#039;,&lt;br /&gt;
                ctype: &#039;text/plain&#039;&lt;br /&gt;
            });&lt;br /&gt;
        } else {&lt;br /&gt;
            url = &#039;/index.php?title=&#039; + encodeURIComponent(title) + &#039;&amp;amp;action=raw&amp;amp;ctype=text/plain&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, title);&lt;br /&gt;
        }&lt;br /&gt;
        return url;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (viewer &amp;amp;&amp;amp; viewer.parentNode) {&lt;br /&gt;
            viewer.parentNode.removeChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;.clbi-system-original-source-hidden&#039;).forEach(function (node) {&lt;br /&gt;
            node.classList.remove(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-system-source-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureSystemDocSourceViewerForShell() {&lt;br /&gt;
        var output = getSystemDocOutputForShell();&lt;br /&gt;
        var source;&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var fallbackText;&lt;br /&gt;
&lt;br /&gt;
        if (!output || !isMediaWikiSystemAssetPageForShell()) return null;&lt;br /&gt;
&lt;br /&gt;
        cleanupLegacySystemDocCodeMutationsForShell();&lt;br /&gt;
&lt;br /&gt;
        source = findSystemDocSourceNodeForShell();&lt;br /&gt;
        if (!source) return null;&lt;br /&gt;
&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!viewer) {&lt;br /&gt;
            viewer = document.createElement(&#039;pre&#039;);&lt;br /&gt;
            viewer.id = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            viewer.className = &#039;clbi-system-source-viewer&#039;;&lt;br /&gt;
            output.appendChild(viewer);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fallbackText = source.textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!viewer.textContent &amp;amp;&amp;amp; fallbackText) {&lt;br /&gt;
            viewer.textContent = fallbackText;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        source.classList.add(&#039;clbi-system-original-source-hidden&#039;);&lt;br /&gt;
        source.setAttribute(&#039;data-clbi-system-source-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        source.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
        return viewer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocSourceViewerForShell() {&lt;br /&gt;
        var viewer;&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var token;&lt;br /&gt;
        var currentScrollTop;&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = String(mw.config.get(&#039;wgPageName&#039;) || readCurrentPageNameForShell());&lt;br /&gt;
        viewer = document.getElementById(&#039;clbi-system-source-viewer&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        시스템 문서 뷰어가 이미 만들어져 있고 raw 원문도 로드된 상태라면&lt;br /&gt;
        다시 source 탐색/숨김/스타일 재적용을 하지 않는다.&lt;br /&gt;
        DevTools Elements 패널에서 body가 계속 파랗게 깜빡이던 원인은&lt;br /&gt;
        MutationObserver가 이 재적용을 반복해서 DOM attribute mutation을 만들었기 때문이다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            viewer &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-title&#039;) === pageName &amp;amp;&amp;amp;&lt;br /&gt;
            viewer.getAttribute(&#039;data-clbi-raw-loaded&#039;) === &#039;1&#039;&lt;br /&gt;
        ) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        viewer = ensureSystemDocSourceViewerForShell();&lt;br /&gt;
        if (!viewer) return;&lt;br /&gt;
&lt;br /&gt;
        currentScrollTop = viewer.scrollTop || 0;&lt;br /&gt;
        viewer.setAttribute(&#039;data-clbi-raw-title&#039;, pageName);&lt;br /&gt;
        token = ++systemDocRawFetchToken;&lt;br /&gt;
&lt;br /&gt;
        fetch(getSystemDocRawUrlForShell(), { credentials: &#039;same-origin&#039; })&lt;br /&gt;
            .then(function (res) {&lt;br /&gt;
                if (!res.ok) throw new Error(&#039;raw fetch failed &#039; + res.status);&lt;br /&gt;
                return res.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (token !== systemDocRawFetchToken) return;&lt;br /&gt;
&lt;br /&gt;
                currentScrollTop = viewer.scrollTop || currentScrollTop || 0;&lt;br /&gt;
&lt;br /&gt;
                if (text &amp;amp;&amp;amp; viewer.textContent !== text) {&lt;br /&gt;
                    viewer.textContent = text;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;1&#039;);&lt;br /&gt;
                viewer.scrollTop = currentScrollTop;&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                viewer.setAttribute(&#039;data-clbi-raw-loaded&#039;, &#039;0&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removeSystemDocIndicatorForShell() {&lt;br /&gt;
        var existing = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (document.body) {&lt;br /&gt;
            document.body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (existing &amp;amp;&amp;amp; existing.parentNode) {&lt;br /&gt;
            existing.parentNode.removeChild(existing);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        removeSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderSystemDocIndicatorForShell() {&lt;br /&gt;
        var pageName;&lt;br /&gt;
        var extMatch;&lt;br /&gt;
        var ext;&lt;br /&gt;
        var row;&lt;br /&gt;
        var box;&lt;br /&gt;
        var meta;&lt;br /&gt;
        var label;&lt;br /&gt;
        var type;&lt;br /&gt;
        var title;&lt;br /&gt;
        var anchor;&lt;br /&gt;
        var main;&lt;br /&gt;
&lt;br /&gt;
        if (!document.body || !isMediaWikiSystemAssetPageForShell()) return;&lt;br /&gt;
&lt;br /&gt;
        pageName = readCurrentPageNameForShell();&lt;br /&gt;
        extMatch = pageName.match(/\.(css|js)$/i);&lt;br /&gt;
        ext = extMatch ? extMatch[1].toUpperCase() : &#039;DOC&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.classList.add(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        row = document.getElementById(&#039;clbi-system-doc-indicator-row&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!row) {&lt;br /&gt;
            row = document.createElement(&#039;div&#039;);&lt;br /&gt;
            row.id = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
            row.className = &#039;clbi-system-doc-indicator-row&#039;;&lt;br /&gt;
&lt;br /&gt;
            box = document.createElement(&#039;div&#039;);&lt;br /&gt;
            box.className = &#039;clbi-system-doc-indicator&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta = document.createElement(&#039;div&#039;);&lt;br /&gt;
            meta.className = &#039;clbi-system-doc-meta&#039;;&lt;br /&gt;
&lt;br /&gt;
            label = document.createElement(&#039;span&#039;);&lt;br /&gt;
            label.className = &#039;clbi-system-doc-label&#039;;&lt;br /&gt;
            label.textContent = &#039;SYSTEM DOCUMENT&#039;;&lt;br /&gt;
&lt;br /&gt;
            type = document.createElement(&#039;span&#039;);&lt;br /&gt;
            type.className = &#039;clbi-system-doc-type&#039;;&lt;br /&gt;
&lt;br /&gt;
            title = document.createElement(&#039;div&#039;);&lt;br /&gt;
            title.className = &#039;clbi-system-doc-title&#039;;&lt;br /&gt;
&lt;br /&gt;
            meta.appendChild(label);&lt;br /&gt;
            meta.appendChild(type);&lt;br /&gt;
            box.appendChild(meta);&lt;br /&gt;
            box.appendChild(title);&lt;br /&gt;
            row.appendChild(box);&lt;br /&gt;
&lt;br /&gt;
            anchor = getSystemDocOutputForShell();&lt;br /&gt;
            main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (anchor &amp;amp;&amp;amp; anchor.parentNode) {&lt;br /&gt;
                anchor.parentNode.insertBefore(row, anchor);&lt;br /&gt;
            } else if (main) {&lt;br /&gt;
                main.insertBefore(row, main.firstChild);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        type = row.querySelector(&#039;.clbi-system-doc-type&#039;);&lt;br /&gt;
        title = row.querySelector(&#039;.clbi-system-doc-title&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (type) type.textContent = ext;&lt;br /&gt;
        if (title) title.textContent = pageName;&lt;br /&gt;
&lt;br /&gt;
        renderSystemDocSourceViewerForShell();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var PAGE_TITLE_TARGET_SELECTORS = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var pageShellObserverStarted = false;&lt;br /&gt;
    var pageShellObserverTimer = null;&lt;br /&gt;
&lt;br /&gt;
    function setPageTitleDomHidden(hidden) {&lt;br /&gt;
        var nodes = document.querySelectorAll(PAGE_TITLE_TARGET_SELECTORS.join(&#039;,&#039;));&lt;br /&gt;
&lt;br /&gt;
        nodes.forEach(function (node) {&lt;br /&gt;
            if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
            if (hidden) {&lt;br /&gt;
                node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
                node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
                node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
                node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClasses() {&lt;br /&gt;
        var body = document.body;&lt;br /&gt;
        var isSystemPage;&lt;br /&gt;
&lt;br /&gt;
        if (!body) return;&lt;br /&gt;
&lt;br /&gt;
        isSystemPage = isBackendOrSystemPageForShell();&lt;br /&gt;
&lt;br /&gt;
        body.classList.remove(&#039;page-title-hidden&#039;, &#039;page-title-visible&#039;, &#039;backend-system-page&#039;, &#039;anecdote-namespace-page&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.remove(&#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            removeSystemDocIndicatorForShell();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isAnecdoteNamespaceForShell()) {&lt;br /&gt;
            body.classList.add(&#039;anecdote-namespace-page&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;, &#039;backend-system-page&#039;, &#039;clbi-system-doc-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
            renderSystemDocIndicatorForShell();&lt;br /&gt;
        } else if (isSystemPage) {&lt;br /&gt;
            body.classList.add(&#039;page-title-visible&#039;, &#039;backend-system-page&#039;);&lt;br /&gt;
            setPageTitleDomHidden(false);&lt;br /&gt;
        } else {&lt;br /&gt;
            body.classList.add(&#039;page-title-hidden&#039;);&lt;br /&gt;
            setPageTitleDomHidden(true);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPageShellClassesDeferred() {&lt;br /&gt;
        applyPageShellClasses();&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 0);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 80);&lt;br /&gt;
        window.setTimeout(applyPageShellClasses, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function startPageShellObserver() {&lt;br /&gt;
        var observer;&lt;br /&gt;
&lt;br /&gt;
        if (pageShellObserverStarted || !window.MutationObserver || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
        pageShellObserverStarted = true;&lt;br /&gt;
        observer = new MutationObserver(function (mutations) {&lt;br /&gt;
            var i;&lt;br /&gt;
            var target;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
            시스템 CSS/JS 문서는 applyPageShellClasses()가 초기에 한 번&lt;br /&gt;
            인디케이터와 source viewer를 만든 뒤에는 MutationObserver가 다시&lt;br /&gt;
            같은 렌더링을 반복할 필요가 없다. 이 반복이 DevTools에서 body/요소가&lt;br /&gt;
            계속 플래시되는 직접 원인이다.&lt;br /&gt;
            SPA 전환 뒤의 처리는 loadPage()와 wikipage.content hook에서 따로 호출된다.&lt;br /&gt;
            */&lt;br /&gt;
            if (isMediaWikiSystemAssetPageForShell()) {&lt;br /&gt;
                for (i = 0; i &amp;lt; mutations.length; i += 1) {&lt;br /&gt;
                    target = mutations[i] &amp;amp;&amp;amp; mutations[i].target;&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        target &amp;amp;&amp;amp;&lt;br /&gt;
                        target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
                        (&lt;br /&gt;
                            target.id === &#039;clbi-system-source-viewer&#039; ||&lt;br /&gt;
                            target.id === &#039;clbi-system-doc-indicator-row&#039; ||&lt;br /&gt;
                            (target.closest &amp;amp;&amp;amp; target.closest(&#039;#clbi-system-source-viewer, #clbi-system-doc-indicator-row&#039;))&lt;br /&gt;
                        )&lt;br /&gt;
                    ) {&lt;br /&gt;
                        return;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-doc-indicator-row&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
                    document.getElementById(&#039;clbi-system-source-viewer&#039;)&lt;br /&gt;
                ) {&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (pageShellObserverTimer) return;&lt;br /&gt;
&lt;br /&gt;
            pageShellObserverTimer = window.setTimeout(function () {&lt;br /&gt;
                pageShellObserverTimer = null;&lt;br /&gt;
                applyPageShellClasses();&lt;br /&gt;
            }, 50);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        observer.observe(document.body, {&lt;br /&gt;
            childList: true,&lt;br /&gt;
            subtree: true&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, function () {&lt;br /&gt;
            applyPageShellClassesDeferred();&lt;br /&gt;
            startPageShellObserver();&lt;br /&gt;
        });&lt;br /&gt;
    } else {&lt;br /&gt;
        applyPageShellClassesDeferred();&lt;br /&gt;
        startPageShellObserver();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(applyPageShellClassesDeferred);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_PAGE_SHELL = {&lt;br /&gt;
        refresh: applyPageShellClasses,&lt;br /&gt;
        isBackendOrSystemPage: isBackendOrSystemPageForShell,&lt;br /&gt;
        isSystemAssetPage: isMediaWikiSystemAssetPageForShell,&lt;br /&gt;
        renderSystemDocIndicator: renderSystemDocIndicatorForShell,&lt;br /&gt;
        removeSystemDocIndicator: removeSystemDocIndicatorForShell,&lt;br /&gt;
        refreshSystemDocSourceViewer: renderSystemDocSourceViewerForShell&lt;br /&gt;
    };&lt;br /&gt;
}());&lt;br /&gt;
&lt;br /&gt;
function loadLangScript(done) {&lt;br /&gt;
    $.getScript(&#039;/index.php?title=미디어위키:Lang.js&amp;amp;action=raw&amp;amp;ctype=text/javascript&#039;)&lt;br /&gt;
        .done(function() {&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        })&lt;br /&gt;
        .fail(function(a, b, c) {&lt;br /&gt;
            console.error(&#039;Lang.js load failed:&#039;, b, c);&lt;br /&gt;
            if (typeof done === &#039;function&#039;) done();&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initHalftoneBackground() {&lt;br /&gt;
    try {&lt;br /&gt;
        initWebGLHalftoneBackground();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;WebGL halftone background failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initWebGLHalftoneBackground() {&lt;br /&gt;
    var canvasId = &#039;site-halftone-bg&#039;;&lt;br /&gt;
    var existing = document.getElementById(canvasId);&lt;br /&gt;
    var canvas = existing || document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    var halftoneState = window.SiteHalftoneBackgroundState || (window.SiteHalftoneBackgroundState = { runId: 0 });&lt;br /&gt;
    var runId = halftoneState.runId + 1;&lt;br /&gt;
&lt;br /&gt;
    halftoneState.runId = runId;&lt;br /&gt;
&lt;br /&gt;
    if (!existing) {&lt;br /&gt;
        canvas.id = canvasId;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        document.body.insertBefore(canvas, document.body.firstChild || null);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canvas.style.position = &#039;fixed&#039;;&lt;br /&gt;
    canvas.style.inset = &#039;0&#039;;&lt;br /&gt;
    canvas.style.width = &#039;100vw&#039;;&lt;br /&gt;
    canvas.style.height = &#039;100vh&#039;;&lt;br /&gt;
    canvas.style.pointerEvents = &#039;none&#039;;&lt;br /&gt;
    canvas.style.background = &#039;#000000&#039;;&lt;br /&gt;
    canvas.style.display = &#039;block&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!canvas.getAttribute(&#039;data-halftone-context-watch&#039;)) {&lt;br /&gt;
        canvas.setAttribute(&#039;data-halftone-context-watch&#039;, &#039;1&#039;);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;, function (event) {&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            canvas.style.display = &#039;none&#039;;&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = true;&lt;br /&gt;
                window.SiteHalftoneBackgroundState.runId += 1;&lt;br /&gt;
            }&lt;br /&gt;
        }, false);&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextrestored&#039;, function () {&lt;br /&gt;
            if (window.SiteHalftoneBackgroundState) {&lt;br /&gt;
                window.SiteHalftoneBackgroundState.contextLost = false;&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(initWebGLHalftoneBackground, 0);&lt;br /&gt;
        }, false);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gl = canvas.getContext(&#039;webgl&#039;, {&lt;br /&gt;
        alpha: false,&lt;br /&gt;
        antialias: false,&lt;br /&gt;
        depth: false,&lt;br /&gt;
        stencil: false,&lt;br /&gt;
        preserveDrawingBuffer: false,&lt;br /&gt;
        powerPreference: &#039;low-power&#039;&lt;br /&gt;
    }) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!gl) {&lt;br /&gt;
        canvas.style.display = &#039;none&#039;;&lt;br /&gt;
        console.warn(&#039;WebGL background unavailable.&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexSrc = [&lt;br /&gt;
        &#039;attribute vec2 a_position;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_position, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var fragmentSrc = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_resolution;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;const float TAU = 6.28318530718;&#039;,&lt;br /&gt;
        &#039;float gaussian(float v, float r) {&#039;,&lt;br /&gt;
        &#039;  return exp(-((v * v) / max(0.0001, r * r)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float hash(vec2 p) {&#039;,&lt;br /&gt;
        &#039;  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;float bucketAlpha(float a) {&#039;,&lt;br /&gt;
        &#039;  float i = floor(a * 9.0);&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 1.0) return 0.040;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 2.0) return 0.080;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 3.0) return 0.135;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 4.0) return 0.210;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 5.0) return 0.310;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 6.0) return 0.430;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 7.0) return 0.580;&#039;,&lt;br /&gt;
        &#039;  if (i &amp;lt; 8.0) return 0.760;&#039;,&lt;br /&gt;
        &#039;  return 0.920;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 frag = gl_FragCoord.xy;&#039;,&lt;br /&gt;
        &#039;  float spacing = 5.0;&#039;,&lt;br /&gt;
        &#039;  float dotSize = 1.08;&#039;,&lt;br /&gt;
        &#039;  vec2 grid = floor(frag / spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 inCell = mod(frag, spacing);&#039;,&lt;br /&gt;
        &#039;  vec2 dotOrigin = vec2(1.0, 1.0);&#039;,&lt;br /&gt;
        &#039;  vec2 dotCenter = dotOrigin + vec2(dotSize * 0.5);&#039;,&lt;br /&gt;
        &#039;  vec2 local = abs(inCell - dotCenter);&#039;,&lt;br /&gt;
        &#039;  float noise = hash(grid);&#039;,&lt;br /&gt;
        &#039;  float size = dotSize + noise * 0.18;&#039;,&lt;br /&gt;
        &#039;  float dotMask = 1.0 - smoothstep(size * 0.5, size * 0.5 + 0.22, max(local.x, local.y));&#039;,&lt;br /&gt;
        &#039;  vec2 uv = frag / u_resolution;&#039;,&lt;br /&gt;
        &#039;  float centerLine = 0.50 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 1.32 + 0.08) * TAU) * 0.070 +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 3.18 + 0.34) * TAU) * 0.030;&#039;,&lt;br /&gt;
        &#039;  float u = uv.x - centerLine;&#039;,&lt;br /&gt;
        &#039;  float absU = abs(u);&#039;,&lt;br /&gt;
        &#039;  float sideLift = smoothstep(0.065, 0.44, absU);&#039;,&lt;br /&gt;
        &#039;  float valley = gaussian(u, 0.150);&#039;,&lt;br /&gt;
        &#039;  float t = u_time;&#039;,&lt;br /&gt;
        &#039;  float leftRibbonCenter = -0.28 + sin((uv.y * 3.20 + 0.12) * TAU) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float rightRibbonCenter = 0.27 + sin((uv.y * 2.85 + 0.56) * TAU) * 0.055;&#039;,&lt;br /&gt;
        &#039;  float leftRibbon = gaussian(u - leftRibbonCenter, 0.105);&#039;,&lt;br /&gt;
        &#039;  float rightRibbon = gaussian(u - rightRibbonCenter, 0.110);&#039;,&lt;br /&gt;
        &#039;  float foldedU = u +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 4.40 + 0.22) * TAU) * 0.050 * (0.3 + sideLift) +&#039;,&lt;br /&gt;
        &#039;    sin((uv.y * 7.20 + uv.x * 1.10) * TAU) * 0.022;&#039;,&lt;br /&gt;
        &#039;  float verticalFold = pow(0.5 + 0.5 * cos(((foldedU * 3.05) + (sin(uv.y * TAU * 2.35) * 0.18)) * TAU), 2.5);&#039;,&lt;br /&gt;
        &#039;  float diagonalFold = pow(0.5 + 0.5 * cos(((foldedU * 1.80) - (uv.y * 1.12) + 0.18) * TAU), 2.1);&#039;,&lt;br /&gt;
        &#039;  float waist = gaussian(uv.y - 0.50, 0.25) * gaussian(absU - 0.20, 0.19);&#039;,&lt;br /&gt;
        &#039;  float grain = (noise - 0.5) * 0.050;&#039;,&lt;br /&gt;
        &#039;  float staticField =&#039;,&lt;br /&gt;
        &#039;    0.055 +&#039;,&lt;br /&gt;
        &#039;    sideLift * 0.210 +&#039;,&lt;br /&gt;
        &#039;    (leftRibbon + rightRibbon) * 0.145 +&#039;,&lt;br /&gt;
        &#039;    verticalFold * (0.055 + sideLift * 0.115) +&#039;,&lt;br /&gt;
        &#039;    diagonalFold * 0.045 +&#039;,&lt;br /&gt;
        &#039;    waist * 0.060 -&#039;,&lt;br /&gt;
        &#039;    valley * 0.150 +&#039;,&lt;br /&gt;
        &#039;    grain;&#039;,&lt;br /&gt;
        &#039;  float alpha = staticField;&#039;,&lt;br /&gt;
        &#039;  alpha += 0.115 * (leftRibbon + rightRibbon) * sin(t * 0.00030 + ((uv.y * 1.9) + sideLift * 0.4) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.095 * verticalFold * (0.4 + sideLift) * sin(t * 0.00041 + ((uv.y * 2.7) + foldedU * 0.65) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.070 * waist * sin(t * 0.00053 + ((uv.y * 3.1) - absU * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.060 * (1.0 - valley) * diagonalFold * sin(t * 0.00067 + ((uv.y * 1.4) + uv.x * 0.6) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha += 0.038 * (0.35 + sideLift) * (0.35 + noise) * sin(t * 0.00079 + ((uv.y * 4.6) + noise * 0.8) * TAU);&#039;,&lt;br /&gt;
        &#039;  alpha = bucketAlpha(clamp(alpha, 0.025, 0.96));&#039;,&lt;br /&gt;
        &#039;  float value = alpha * dotMask;&#039;,&lt;br /&gt;
        &#039;  gl_FragColor = vec4(vec3(0.8862745 * value), 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function compileShader(type, source) {&lt;br /&gt;
        var shader = gl.createShader(type);&lt;br /&gt;
        gl.shaderSource(shader, source);&lt;br /&gt;
        gl.compileShader(shader);&lt;br /&gt;
&lt;br /&gt;
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {&lt;br /&gt;
            console.error(&#039;WebGL shader compile error:&#039;, gl.getShaderInfoLog(shader));&lt;br /&gt;
            gl.deleteShader(shader);&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return shader;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var vertexShader = compileShader(gl.VERTEX_SHADER, vertexSrc);&lt;br /&gt;
    var fragmentShader = compileShader(gl.FRAGMENT_SHADER, fragmentSrc);&lt;br /&gt;
&lt;br /&gt;
    if (!vertexShader || !fragmentShader) return;&lt;br /&gt;
&lt;br /&gt;
    var program = gl.createProgram();&lt;br /&gt;
    gl.attachShader(program, vertexShader);&lt;br /&gt;
    gl.attachShader(program, fragmentShader);&lt;br /&gt;
    gl.linkProgram(program);&lt;br /&gt;
&lt;br /&gt;
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {&lt;br /&gt;
        console.error(&#039;WebGL program link error:&#039;, gl.getProgramInfoLog(program));&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var positionLoc = gl.getAttribLocation(program, &#039;a_position&#039;);&lt;br /&gt;
    var resolutionLoc = gl.getUniformLocation(program, &#039;u_resolution&#039;);&lt;br /&gt;
    var timeLoc = gl.getUniformLocation(program, &#039;u_time&#039;);&lt;br /&gt;
&lt;br /&gt;
    var buffer = gl.createBuffer();&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([&lt;br /&gt;
        -1, -1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
        -1,  1,&lt;br /&gt;
         1, -1,&lt;br /&gt;
         1,  1&lt;br /&gt;
    ]), gl.STATIC_DRAW);&lt;br /&gt;
&lt;br /&gt;
    gl.useProgram(program);&lt;br /&gt;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);&lt;br /&gt;
    gl.enableVertexAttribArray(positionLoc);&lt;br /&gt;
    gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
    function resize() {&lt;br /&gt;
        var dpr = Math.min(window.devicePixelRatio || 1, 1.5);&lt;br /&gt;
        var cssW = Math.max(1, window.innerWidth || document.documentElement.clientWidth || 1);&lt;br /&gt;
        var cssH = Math.max(1, window.innerHeight || document.documentElement.clientHeight || 1);&lt;br /&gt;
        var w = Math.max(1, Math.floor(cssW * dpr));&lt;br /&gt;
        var h = Math.max(1, Math.floor(cssH * dpr));&lt;br /&gt;
&lt;br /&gt;
        if (canvas.width !== w || canvas.height !== h) {&lt;br /&gt;
            canvas.width = w;&lt;br /&gt;
            canvas.height = h;&lt;br /&gt;
            canvas.style.width = cssW + &#039;px&#039;;&lt;br /&gt;
            canvas.style.height = cssH + &#039;px&#039;;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var prefersReducedMotion = false;&lt;br /&gt;
    try {&lt;br /&gt;
        prefersReducedMotion = window.matchMedia &amp;amp;&amp;amp; window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches;&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneNode() {&lt;br /&gt;
        return document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe], [data-nations-globe=&amp;quot;real-world&amp;quot;]&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getNationsGlobeHalftoneState() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) {&lt;br /&gt;
            return &#039;none&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) {&lt;br /&gt;
            return &#039;active&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundBusy === &#039;function&#039; &amp;amp;&amp;amp; instance.isHalftoneBackgroundBusy()) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) {&lt;br /&gt;
            return &#039;busy&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;present&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function shouldSkipHalftoneDrawForGlobe() {&lt;br /&gt;
        var globe = getNationsGlobeHalftoneNode();&lt;br /&gt;
        var instance;&lt;br /&gt;
&lt;br /&gt;
        if (!globe) return false;&lt;br /&gt;
&lt;br /&gt;
        instance = globe.CLBI_NationsGlobeInstance || null;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Background pause/resume — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * While the nations globe is being held, keep the last rendered halftone&lt;br /&gt;
         * frame on screen and pause the halftone clock.  The important detail is&lt;br /&gt;
         * that this is not a wall-clock catch-up: skipped seconds are not replayed&lt;br /&gt;
         * or fast-forwarded when the pointer is released.&lt;br /&gt;
         */&lt;br /&gt;
        if (globe.classList &amp;amp;&amp;amp; globe.classList.contains(&#039;is-dragging&#039;)) return true;&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; instance.dragging) return true;&lt;br /&gt;
&lt;br /&gt;
        if (instance &amp;amp;&amp;amp; typeof instance.isHalftoneBackgroundHardBusy === &#039;function&#039;) {&lt;br /&gt;
            return instance.isHalftoneBackgroundHardBusy();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (globe.getAttribute &amp;amp;&amp;amp; globe.getAttribute(&#039;data-clbi-globe-bg-busy&#039;) === &#039;1&#039;) return true;&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFrameInterval() {&lt;br /&gt;
        if (prefersReducedMotion) {&lt;br /&gt;
            return 1000;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Nations background parity — 20260710.&lt;br /&gt;
         *&lt;br /&gt;
         * The nations page used to permanently lower the halftone cadence while&lt;br /&gt;
         * the globe existed.  Profiling showed that the steady halftone draw is&lt;br /&gt;
         * normally cheap; the real contention happens during globe drag, first&lt;br /&gt;
         * WebGL texture upload, and long layout/render tasks.  Keep the same&lt;br /&gt;
         * full-quality cadence as ordinary pages, and skip only the frames that&lt;br /&gt;
         * would directly collide with an active/busy globe moment.&lt;br /&gt;
         */&lt;br /&gt;
        return 66;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var lastFrame = 0;&lt;br /&gt;
    var startTime = performance.now();&lt;br /&gt;
    var visualTime = 0;&lt;br /&gt;
    var lastVisualNow = 0;&lt;br /&gt;
&lt;br /&gt;
    function draw(now) {&lt;br /&gt;
        var state = getNationsGlobeHalftoneState();&lt;br /&gt;
        var interval = getFrameInterval();&lt;br /&gt;
        var perf = window.InteractionPerf;&lt;br /&gt;
        var delta;&lt;br /&gt;
&lt;br /&gt;
        if (!lastVisualNow) {&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
        }&lt;br /&gt;
        delta = Math.max(0, Math.min(120, now - lastVisualNow));&lt;br /&gt;
        lastVisualNow = now;&lt;br /&gt;
        visualTime += delta;&lt;br /&gt;
&lt;br /&gt;
        function runDraw() {&lt;br /&gt;
            resize();&lt;br /&gt;
&lt;br /&gt;
            gl.clearColor(0, 0, 0, 1);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.uniform2f(resolutionLoc, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform1f(timeLoc, visualTime);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLES, 0, 6);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (perf &amp;amp;&amp;amp; typeof perf.measureSync === &#039;function&#039;) {&lt;br /&gt;
            return perf.measureSync(&#039;background halftone draw&#039;, { globeState: state, interval: interval }, runDraw);&lt;br /&gt;
        }&lt;br /&gt;
        return runDraw();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(now) {&lt;br /&gt;
        if (halftoneState.runId !== runId || halftoneState.contextLost) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(render);&lt;br /&gt;
&lt;br /&gt;
        if (document.hidden) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (now - lastFrame &amp;lt; getFrameInterval()) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (shouldSkipHalftoneDrawForGlobe()) {&lt;br /&gt;
            lastFrame = now;&lt;br /&gt;
            halftoneState.lastGlobeBusySkipAt = now;&lt;br /&gt;
&lt;br /&gt;
            /*&lt;br /&gt;
             * Pause the halftone clock while a hard-busy globe frame owns the&lt;br /&gt;
             * budget.  Without this, the next visible background frame catches&lt;br /&gt;
             * up to wall-clock time and the pattern appears to skip forward.&lt;br /&gt;
             * Updating lastVisualNow here makes the animation resume from the&lt;br /&gt;
             * last visible moment instead of replaying the hidden gap.&lt;br /&gt;
             */&lt;br /&gt;
            lastVisualNow = now;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        draw(now);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    draw(performance.now());&lt;br /&gt;
    requestAnimationFrame(render);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SVG_BELL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_BELL_DOT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-bell-dot&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10.268 21a2 2 0 0 0 3.464 0&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M11.68 2.009A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673c-.824-.85-1.678-1.731-2.21-3.348&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;18&amp;quot; cy=&amp;quot;5&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LIST = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-list&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 5h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 12h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3 19h.01&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 5h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 12h13&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M8 19h13&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_LANGUAGES = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-languages&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;m5 8 6 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m4 14 6-6 2-3&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M2 5h12&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 2h1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m22 22-5-10-5 10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 18h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_POWER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-power&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 2v10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.4 6.6a9 9 0 1 1-12.77.04&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SETTINGS = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-settings&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_TEXT = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-text&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 8h8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 12h10&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 16h6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_SCAN_EYE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-scan-eye&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M3 7V5a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M17 3h2a2 2 0 0 1 2 2v2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M21 17v2a2 2 0 0 1-2 2h-2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M7 21H5a2 2 0 0 1-2-2v-2&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;12&amp;quot; cy=&amp;quot;12&amp;quot; r=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18.944 12.33a1 1 0 0 0 0-.66 7.5 7.5 0 0 0-13.888 0 1 1 0 0 0 0 .66 7.5 7.5 0 0 0 13.888 0&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_NEWSPAPER = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-newspaper&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M15 18h-5&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 14h-8&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-4 0v-9a2 2 0 0 1 2-2h2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;8&amp;quot; height=&amp;quot;4&amp;quot; x=&amp;quot;10&amp;quot; y=&amp;quot;6&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_GREAT_WALL = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-great-wall lucide lucide-paint-roller&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;rect width=&amp;quot;16&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;2&amp;quot; y=&amp;quot;2&amp;quot; rx=&amp;quot;2&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M10 16v-2a2 2 0 0 1 2-2h8a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2&amp;quot;/&amp;gt;&amp;lt;rect width=&amp;quot;4&amp;quot; height=&amp;quot;6&amp;quot; x=&amp;quot;8&amp;quot; y=&amp;quot;16&amp;quot; rx=&amp;quot;1&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_TROPHY = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-trophy&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 9h1.5a1 1 0 0 0 0-5H18&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M4 22h16&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M6 9H4.5a1 1 0 0 1 0-5H6&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
var CLBI_SVG_PACKAGE = &#039;&amp;lt;svg class=&amp;quot;profile-svg profile-svg-package&amp;quot; xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;2.75&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;M12 3v6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M16.76 3a2 2 0 0 1 1.8 1.1l2.23 4.479a2 2 0 0 1 .21.891V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9.472a2 2 0 0 1 .211-.894L5.45 4.1A2 2 0 0 1 7.24 3z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M3.054 9.013h17.893&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
var PROFILE_RENDER_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function invalidateProfileRender() {&lt;br /&gt;
    PROFILE_RENDER_TOKEN++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    initHalftoneBackground();&lt;br /&gt;
&lt;br /&gt;
// ── 하단 Plank 단축키 가이드 ──&lt;br /&gt;
function escapeClbiBottomGuideHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiBottomGuidePageName() {&lt;br /&gt;
    var pageName = window.mw &amp;amp;&amp;amp; mw.config ? (mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) {&lt;br /&gt;
        pageName = window.location.pathname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return String(pageName)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsShortcutContext() {&lt;br /&gt;
    var pageName = readClbiBottomGuidePageName();&lt;br /&gt;
&lt;br /&gt;
    return pageName === &#039;시대&#039; ||&lt;br /&gt;
        pageName === &#039;Era&#039; ||&lt;br /&gt;
        !!document.querySelector(&#039;.clbi-nations-panel-stack, .clbi-nations-globe-window, .clbi-nations-tabpanel&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiWikiEditShortcutContext() {&lt;br /&gt;
    var action = window.mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgAction&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    var search = String(window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
    return action === &#039;edit&#039; ||&lt;br /&gt;
        action === &#039;submit&#039; ||&lt;br /&gt;
        /(?:^|[?&amp;amp;])action=(?:edit|submit)(?:&amp;amp;|$)/.test(search) ||&lt;br /&gt;
        !!document.querySelector(&#039;#editform, #wpSave, input[name=&amp;quot;wpSave&amp;quot;], button[name=&amp;quot;wpSave&amp;quot;]&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function isClbiNationListManagerShortcutContext() {&lt;br /&gt;
    return !!document.querySelector(&#039;.nation-list-manager&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiBottomShortcutItems() {&lt;br /&gt;
    if (isClbiWikiEditShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;변경사항 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationListManagerShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Ctrl+S&#039;, label: &#039;국가 목록 저장&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsShortcutContext()) {&lt;br /&gt;
        return [&lt;br /&gt;
            { key: &#039;Q&#039;, label: &#039;이전 시대&#039; },&lt;br /&gt;
            { key: &#039;E&#039;, label: &#039;다음 시대&#039; },&lt;br /&gt;
            { key: &#039;Shift+Q&#039;, label: &#039;이전 연도&#039; },&lt;br /&gt;
            { key: &#039;Shift+E&#039;, label: &#039;다음 연도&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+Q&#039;, label: &#039;이전 대륙&#039; },&lt;br /&gt;
            { key: &#039;Ctrl+E&#039;, label: &#039;다음 대륙&#039; }&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return [];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderClbiBottomShortcutGuide() {&lt;br /&gt;
    var guide = document.getElementById(&#039;clbi-bottom-shortcut-guide&#039;);&lt;br /&gt;
    var items;&lt;br /&gt;
    var html;&lt;br /&gt;
&lt;br /&gt;
    if (!guide) return;&lt;br /&gt;
&lt;br /&gt;
    items = getClbiBottomShortcutItems();&lt;br /&gt;
&lt;br /&gt;
    if (!items.length) {&lt;br /&gt;
        guide.classList.add(&#039;is-empty&#039;);&lt;br /&gt;
        guide.innerHTML = &#039;&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    guide.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    html = &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-list&amp;quot;&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        html += &#039;&amp;lt;div class=&amp;quot;clbi-bottom-shortcut-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-key&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.key) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;clbi-bottom-shortcut-label&amp;quot;&amp;gt;&#039; + escapeClbiBottomGuideHtml(item.label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    html += &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    guide.innerHTML = html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiBottomPlankHtml(wrapId, navId, mainId) {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-bottom-shortcut-guide&amp;quot; class=&amp;quot;is-empty&amp;quot; aria-label=&amp;quot;단축키 안내&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
var CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
&lt;br /&gt;
function getClbiNationsTabAtPointer(tabpanel) {&lt;br /&gt;
    var element;&lt;br /&gt;
    var tab;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return null;&lt;br /&gt;
    if (CLBI_NATIONS_LAST_POINTER_X === null || CLBI_NATIONS_LAST_POINTER_Y === null) return null;&lt;br /&gt;
    if (typeof document.elementFromPoint !== &#039;function&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
    element = document.elementFromPoint(CLBI_NATIONS_LAST_POINTER_X, CLBI_NATIONS_LAST_POINTER_Y);&lt;br /&gt;
    if (!element) return null;&lt;br /&gt;
&lt;br /&gt;
    tab = element.closest ? element.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (!tab || !tabpanel.contains(tab)) return null;&lt;br /&gt;
&lt;br /&gt;
    return tab;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateClbiNationsPointerHover(tabpanel, forceSuppress) {&lt;br /&gt;
    var tab = getClbiNationsTabAtPointer(tabpanel);&lt;br /&gt;
    var suppressContinent;&lt;br /&gt;
    var tabContinent;&lt;br /&gt;
    var tabIsActive;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
&lt;br /&gt;
    if (!tab) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabContinent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    tabIsActive = tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (tabIsActive) {&lt;br /&gt;
        clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    suppressContinent = tabpanel.getAttribute(&#039;data-clbi-hover-suppress-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (forceSuppress || !suppressContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-hover-suppress-continent&#039;, tabContinent);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (suppressContinent === tabContinent) {&lt;br /&gt;
        tabpanel.classList.add(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * The pointer actually moved onto another tab after the keyboard switch.&lt;br /&gt;
     * At that point this is no longer stale browser :hover; let normal hover work.&lt;br /&gt;
     */&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function validateAllClbiNationsPointerHovers() {&lt;br /&gt;
    var panels = document.querySelectorAll(&#039;.clbi-nations-tabpanel.is-keyboard-switching&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        if (isClbiNationsPanelOwnedTabpanel(tabpanel)) return;&lt;br /&gt;
        validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsPanelOwnedTabpanel(tabpanel) {&lt;br /&gt;
    /*&lt;br /&gt;
     * Mouse continent tab regression guard.&lt;br /&gt;
     * -------------------------------------&lt;br /&gt;
     * Detached SPA preparation serializes attributes/classes but it cannot&lt;br /&gt;
     * serialize DOM event listeners or JS properties.  The NationsPanel fix&lt;br /&gt;
     * therefore uses the DOM property CLBI_NationsPanelOwned /&lt;br /&gt;
     * CLBI_NationsTabPanelBound as the real ownership marker.&lt;br /&gt;
     *&lt;br /&gt;
     * Do NOT treat data-nations-tabpanel-ready or clbi-nations-continent-cache&lt;br /&gt;
     * as ownership here.  Those can survive innerHTML insertion while the real&lt;br /&gt;
     * click listeners were lost, causing Common.js to delegate to a panel that&lt;br /&gt;
     * NationsPanel has not rebound yet.  The fallback below must remain able to&lt;br /&gt;
     * handle mouse clicks until NationsPanel reclaims the live DOM node.&lt;br /&gt;
     */&lt;br /&gt;
    return !!(tabpanel &amp;amp;&amp;amp; tabpanel.CLBI_NationsPanelOwned);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function isClbiNationsLiveContinentActive(tabpanel, continent) {&lt;br /&gt;
    var tab;&lt;br /&gt;
    var panel;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !continent) return false;&lt;br /&gt;
&lt;br /&gt;
    tab = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panel = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;)).find(function (candidate) {&lt;br /&gt;
        return candidate.getAttribute(&#039;data-continent-panel&#039;) === continent;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return !!(&lt;br /&gt;
        tab &amp;amp;&amp;amp;&lt;br /&gt;
        panel &amp;amp;&amp;amp;&lt;br /&gt;
        (tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.classList.contains(&#039;is-active&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        panel.getAttribute(&#039;aria-hidden&#039;) !== &#039;true&#039; &amp;amp;&amp;amp;&lt;br /&gt;
        !panel.hasAttribute(&#039;hidden&#039;)&lt;br /&gt;
    );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function activateClbiNationsContinent(tabpanel, targetContinent, options) {&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var panels;&lt;br /&gt;
    var skipOwner = !!(options &amp;amp;&amp;amp; options.skipOwner);&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel || !targetContinent) return false;&lt;br /&gt;
&lt;br /&gt;
    if (!skipOwner &amp;amp;&amp;amp; isClbiNationsPanelOwnedTabpanel(tabpanel)) {&lt;br /&gt;
        var owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
&lt;br /&gt;
        if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                if (owner.activateContinent(targetContinent, {&lt;br /&gt;
                    source: &#039;common-legacy-click&#039;,&lt;br /&gt;
                    panel: tabpanel&lt;br /&gt;
                })) {&lt;br /&gt;
                    return true;&lt;br /&gt;
                }&lt;br /&gt;
            } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Ownership markers can be stale during SPA/hydration edge cases.  If&lt;br /&gt;
         * the owner API is missing or refuses the live panel, do not drop the&lt;br /&gt;
         * mouse click.  Fall through to the local fallback so pointer users are&lt;br /&gt;
         * never left with keyboard-only continent tabs.&lt;br /&gt;
         */&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    panels = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-continent[data-continent-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
    if (!tabs.length || !panels.length) return false;&lt;br /&gt;
&lt;br /&gt;
    tabs.forEach(function (tab) {&lt;br /&gt;
        var active = tab.getAttribute(&#039;data-continent&#039;) === targetContinent;&lt;br /&gt;
        tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
        tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
        tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    panels.forEach(function (panel) {&lt;br /&gt;
        var active = panel.getAttribute(&#039;data-continent-panel&#039;) === targetContinent;&lt;br /&gt;
        panel.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
        if (active) {&lt;br /&gt;
            panel.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            panel.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.sync === &#039;function&#039;) window.Decorations.sync();&lt;br /&gt;
        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.sync === &#039;function&#039;) window.CLBI_DECORATIONS.sync();&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    validateClbiNationsPointerHover(tabpanel, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearClbiNationsKeyboardHoverSuppressed(tabpanel) {&lt;br /&gt;
    if (!tabpanel) return;&lt;br /&gt;
    tabpanel.classList.remove(&#039;is-keyboard-switching&#039;);&lt;br /&gt;
    tabpanel.removeAttribute(&#039;data-clbi-hover-suppress-continent&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveClbiNationsContinent(direction) {&lt;br /&gt;
    var tabpanel = document.querySelector(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
    var tabs;&lt;br /&gt;
    var activeIndex;&lt;br /&gt;
    var nextIndex;&lt;br /&gt;
    var target;&lt;br /&gt;
&lt;br /&gt;
    if (!tabpanel) return false;&lt;br /&gt;
&lt;br /&gt;
    if (isClbiNationsPanelOwnedTabpanel(tabpanel) &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI_NATIONS_PANEL &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI_NATIONS_PANEL.moveContinent === &#039;function&#039;) {&lt;br /&gt;
        return !!window.CLBI_NATIONS_PANEL.moveContinent(direction, {&lt;br /&gt;
            source: &#039;common-legacy-keyboard&#039;,&lt;br /&gt;
            panel: tabpanel&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    tabs = Array.prototype.slice.call(tabpanel.querySelectorAll(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;));&lt;br /&gt;
    if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
    activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
        return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
    nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
    target = tabs[nextIndex].getAttribute(&#039;data-continent&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (activateClbiNationsContinent(tabpanel, target)) {&lt;br /&gt;
        setClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        window.setTimeout(function () {&lt;br /&gt;
            validateClbiNationsPointerHover(tabpanel, false);&lt;br /&gt;
        }, 0);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return false;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsTabpanelControls(root) {&lt;br /&gt;
    var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
    var panels = scope.querySelectorAll(&#039;.clbi-nations-tabpanel&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(panels, function (tabpanel) {&lt;br /&gt;
        /*&lt;br /&gt;
         * Common.js is only a safety fallback for continent tabs; NationsPanel&lt;br /&gt;
         * is the primary owner.  This guard must be a DOM property, not a&lt;br /&gt;
         * serialized attribute.  SPA warm prepaint can preserve&lt;br /&gt;
         * data-clbi-nations-tabs-ready=&amp;quot;1&amp;quot; while losing the actual event&lt;br /&gt;
         * listeners, which was the root cause of mouse clicks no longer moving&lt;br /&gt;
         * continents while keyboard shortcuts still worked.&lt;br /&gt;
         */&lt;br /&gt;
        if (tabpanel.CLBI_NationsCommonTabsBound) return;&lt;br /&gt;
        tabpanel.CLBI_NationsCommonTabsBound = true;&lt;br /&gt;
        tabpanel.setAttribute(&#039;data-clbi-nations-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerdown&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
            if (activateClbiNationsContinent(tabpanel, tab.getAttribute(&#039;data-continent&#039;))) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        tabpanel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
            var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;) : null;&lt;br /&gt;
            var handled = false;&lt;br /&gt;
&lt;br /&gt;
            if (!tab || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
            if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsContinent(-1);&lt;br /&gt;
            else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsContinent(1);&lt;br /&gt;
&lt;br /&gt;
            if (handled) {&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                event.stopPropagation();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function handleClbiNationsContinentDocumentClick(event) {&lt;br /&gt;
    var target;&lt;br /&gt;
    var tab;&lt;br /&gt;
    var tabpanel;&lt;br /&gt;
    var continent;&lt;br /&gt;
    var owner;&lt;br /&gt;
    var handled = false;&lt;br /&gt;
&lt;br /&gt;
    if (!event || event.__clbiNationsContinentHandled) return;&lt;br /&gt;
    target = event.target;&lt;br /&gt;
    if (!target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
    tab = target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent]&#039;);&lt;br /&gt;
    if (!tab) return;&lt;br /&gt;
&lt;br /&gt;
    tabpanel = tab.closest ? tab.closest(&#039;.clbi-nations-tabpanel&#039;) : null;&lt;br /&gt;
    if (!tabpanel || !tabpanel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
    continent = tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
    if (!continent) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
     * Final live-DOM mouse delegate.&lt;br /&gt;
     * --------------------------------&lt;br /&gt;
     * The continent tab UI has two initialization phases: detached preparation&lt;br /&gt;
     * for seamless SPA entry, then live DOM hydration.  Detached preparation can&lt;br /&gt;
     * leave serialized &amp;quot;ready&amp;quot; attributes behind while losing event listeners.&lt;br /&gt;
     * Keyboard shortcuts still work because they call the public NationsPanel&lt;br /&gt;
     * API directly, but mouse clicks depend on live listeners.  This capturing&lt;br /&gt;
     * delegate is intentionally independent of per-panel ready attributes: it&lt;br /&gt;
     * always resolves the clicked live tab and hands it to NationsPanel first,&lt;br /&gt;
     * then falls back to the local Common.js switcher if needed.&lt;br /&gt;
     */&lt;br /&gt;
    event.__clbiNationsContinentHandled = true;&lt;br /&gt;
    clearClbiNationsKeyboardHoverSuppressed(tabpanel);&lt;br /&gt;
&lt;br /&gt;
    owner = window.NationsPanel || window.CLBI_NATIONS_PANEL || null;&lt;br /&gt;
    if (owner &amp;amp;&amp;amp; typeof owner.activateContinent === &#039;function&#039;) {&lt;br /&gt;
        try {&lt;br /&gt;
            handled = !!owner.activateContinent(continent, {&lt;br /&gt;
                source: &#039;mouse-click-live-delegate&#039;,&lt;br /&gt;
                panel: tabpanel&lt;br /&gt;
            });&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * Owner API stale-cache guard.&lt;br /&gt;
         *&lt;br /&gt;
         * A previous regression came from NationsPanel returning true after it&lt;br /&gt;
         * updated cached/detached tab nodes instead of the clicked live tab. A&lt;br /&gt;
         * true return value alone is therefore not enough for mouse input. Verify&lt;br /&gt;
         * the live DOM that received the click. If it did not become active,&lt;br /&gt;
         * bypass the owner and run the Common.js live query fallback directly.&lt;br /&gt;
         */&lt;br /&gt;
        if (handled &amp;amp;&amp;amp; !isClbiNationsLiveContinentActive(tabpanel, continent)) {&lt;br /&gt;
            handled = false;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!handled) {&lt;br /&gt;
        handled = activateClbiNationsContinent(tabpanel, continent, { skipOwner: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (handled) {&lt;br /&gt;
        try { playStaticSound(); } catch (err2) {}&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        event.stopPropagation();&lt;br /&gt;
        if (typeof event.stopImmediatePropagation === &#039;function&#039;) event.stopImmediatePropagation();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsGlobalClickDelegate() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-global-click-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-global-click-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, handleClbiNationsContinentDocumentClick, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiNationsPointerHoverValidation() {&lt;br /&gt;
    if (document.body.getAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    document.body.setAttribute(&#039;data-clbi-nations-pointer-hover-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointermove&#039;, function (event) {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = event.clientX;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = event.clientY;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;pointerleave&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;blur&#039;, function () {&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_X = null;&lt;br /&gt;
        CLBI_NATIONS_LAST_POINTER_Y = null;&lt;br /&gt;
        validateAllClbiNationsPointerHovers();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function initClbiBottomShortcutSystem(root) {&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsGlobalClickDelegate();&lt;br /&gt;
    initClbiNationsTabpanelControls(root || document);&lt;br /&gt;
    initClbiNationsPointerHoverValidation();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// ── 상·하단 네비게이션 바 ──&lt;br /&gt;
function buildClbiNavHtml(position) {&lt;br /&gt;
    var isBottom = position === &#039;bottom&#039;;&lt;br /&gt;
    var base = isBottom ? &#039;clbi-bottom&#039; : &#039;clbi-top&#039;;&lt;br /&gt;
    var shortBase = isBottom ? &#039;clbi-bnav&#039; : &#039;clbi-tnav&#039;;&lt;br /&gt;
    var wrapId = base + &#039;-nav-wrap&#039;;&lt;br /&gt;
    var navId = base + &#039;-nav&#039;;&lt;br /&gt;
    var mainId = base + &#039;-nav-main&#039;;&lt;br /&gt;
    var tabsId = base + &#039;-nav-tabs&#039;;&lt;br /&gt;
    var searchId = base + &#039;-nav-search&#039;;&lt;br /&gt;
    var inputId = isBottom ? &#039;clbi-bottom-search-input&#039; : &#039;clbi-top-search-input&#039;;&lt;br /&gt;
    var worldId = shortBase + &#039;-worldbuilding&#039;;&lt;br /&gt;
    var subId = isBottom ? &#039;clbi-bottom-sub-worldbuilding&#039; : &#039;clbi-sub-worldbuilding&#039;;&lt;br /&gt;
    var subInnerId = subId + &#039;-inner&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (isBottom) {&lt;br /&gt;
        return buildClbiBottomPlankHtml(wrapId, navId, mainId);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;&#039; + wrapId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;&#039; + navId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + mainId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + tabsId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/대문&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-main-menu-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;메인 메뉴&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a class=&amp;quot;clbi-top-nav-item&amp;quot; href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-project-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;프로젝트&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-top-nav-item&amp;quot; id=&amp;quot;&#039; + worldId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;clbi-tnav-icon&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Ic-worldbuilding-001.png&amp;quot; alt=&amp;quot;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-label&amp;quot;&amp;gt;세계관&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-tnav-arrow&amp;quot;&amp;gt;▾&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    (isBottom ? &#039;&#039; : (&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;&#039; + searchId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;&#039; + inputId + &#039;&amp;quot; placeholder=&amp;quot;검색...&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                    )) +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;&#039; + subId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;&#039; + subInnerId + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-tnav-sub-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/시대&amp;quot;&amp;gt;시대&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/설정&amp;quot;&amp;gt;설정&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/기업_및_공동체&amp;quot;&amp;gt;기업 및 공동체&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/군_정치집단&amp;quot;&amp;gt;군, 정치집단&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a class=&amp;quot;clbi-tnav-sub-item&amp;quot; href=&amp;quot;/index.php/인물&amp;quot;&amp;gt;인물&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeClbiShellDomOrder() {&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var canvas = document.getElementById(&#039;site-halftone-bg&#039;);&lt;br /&gt;
    var anchor;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var topRect;&lt;br /&gt;
    var wrapperRect;&lt;br /&gt;
    var needsRecovery;&lt;br /&gt;
    var expectedWrapperTop;&lt;br /&gt;
    var host;&lt;br /&gt;
&lt;br /&gt;
    if (!contentWrapper || !topNav || !bottomNav || !document.body) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    CLBI shell can live inside a Liberty &amp;lt;section&amp;gt;.  Some skins/layouts give that&lt;br /&gt;
    section a flow context that lets the top nav visually overlap the content&lt;br /&gt;
    wrapper even when DOM sibling order is correct.  Mark the common parent and&lt;br /&gt;
    let Layout.css force a simple vertical flow for the shell.&lt;br /&gt;
    */&lt;br /&gt;
    host = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp;&lt;br /&gt;
        contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? contentWrapper.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (host) {&lt;br /&gt;
        host.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    viewportH = window.innerHeight || document.documentElement.clientHeight || 0;&lt;br /&gt;
    topRect = topNav.getBoundingClientRect();&lt;br /&gt;
    wrapperRect = contentWrapper.getBoundingClientRect();&lt;br /&gt;
    expectedWrapperTop = topRect.bottom + 8;&lt;br /&gt;
&lt;br /&gt;
    needsRecovery = false;&lt;br /&gt;
&lt;br /&gt;
    if (viewportH &amp;gt; 0) {&lt;br /&gt;
        if (topRect.top &amp;gt;= viewportH * 0.55) needsRecovery = true;&lt;br /&gt;
        if (wrapperRect.top &amp;gt;= viewportH * 0.60) needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (topRect.top &amp;gt; 240 || wrapperRect.top &amp;gt; 320) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (wrapperRect.top &amp;lt; expectedWrapperTop - 1) {&lt;br /&gt;
        needsRecovery = true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!needsRecovery) {&lt;br /&gt;
        document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    anchor = canvas &amp;amp;&amp;amp; canvas.parentNode === document.body&lt;br /&gt;
        ? canvas.nextSibling&lt;br /&gt;
        : document.body.firstChild;&lt;br /&gt;
&lt;br /&gt;
    document.body.insertBefore(topNav, anchor);&lt;br /&gt;
    document.body.insertBefore(contentWrapper, topNav.nextSibling);&lt;br /&gt;
    document.body.insertBefore(bottomNav, contentWrapper.nextSibling);&lt;br /&gt;
&lt;br /&gt;
    document.body.classList.add(&#039;clbi-shell-ready&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.normalizeClbiShellDomOrder = normalizeClbiShellDomOrder;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureClbiVerticalScaleHost() {&lt;br /&gt;
    var topNav = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var contentWrapper = document.querySelector(&#039;.content-wrapper&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var host;&lt;br /&gt;
    var existing;&lt;br /&gt;
    var parent;&lt;br /&gt;
&lt;br /&gt;
    if (!topNav || !contentWrapper || !bottomNav || !document.body) return null;&lt;br /&gt;
&lt;br /&gt;
    existing = document.getElementById(&#039;clbi-shell-scale-host&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (existing &amp;amp;&amp;amp; existing.contains(topNav) &amp;amp;&amp;amp; existing.contains(contentWrapper) &amp;amp;&amp;amp; existing.contains(bottomNav)) {&lt;br /&gt;
        existing.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        return existing;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    parent = topNav.parentElement === contentWrapper.parentElement &amp;amp;&amp;amp; contentWrapper.parentElement === bottomNav.parentElement&lt;br /&gt;
        ? topNav.parentElement&lt;br /&gt;
        : null;&lt;br /&gt;
&lt;br /&gt;
    if (parent &amp;amp;&amp;amp; parent !== document.body) {&lt;br /&gt;
        parent.classList.add(&#039;clbi-shell-host&#039;);&lt;br /&gt;
        parent.id = parent.id || &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
        return parent;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host = existing || document.createElement(&#039;div&#039;);&lt;br /&gt;
    host.id = &#039;clbi-shell-scale-host&#039;;&lt;br /&gt;
    host.className = &#039;clbi-shell-host&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!host.parentNode) {&lt;br /&gt;
        document.body.insertBefore(host, topNav);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    host.appendChild(topNav);&lt;br /&gt;
    host.appendChild(contentWrapper);&lt;br /&gt;
    host.appendChild(bottomNav);&lt;br /&gt;
&lt;br /&gt;
    return host;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function readClbiRootPx(name, fallback) {&lt;br /&gt;
    var raw = &#039;&#039;;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        raw = getComputedStyle(document.documentElement).getPropertyValue(name);&lt;br /&gt;
    } catch (err) {}&lt;br /&gt;
&lt;br /&gt;
    value = parseFloat(raw);&lt;br /&gt;
    return isFinite(value) &amp;amp;&amp;amp; value &amp;gt; 0 ? value : fallback;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftRecentAdaptiveState() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var newsBox = list ? list.closest(&#039;.clbi-left-news-box&#039;) : null;&lt;br /&gt;
    var items = list ? Array.prototype.slice.call(list.querySelectorAll(&#039;.news-recent-item&#039;)) : [];&lt;br /&gt;
&lt;br /&gt;
    if (newsBox) {&lt;br /&gt;
        newsBox.classList.remove(&#039;is-adaptive-constrained&#039;);&lt;br /&gt;
        newsBox.style.removeProperty(&#039;--adaptive-news-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.classList.remove(&#039;is-adaptive-faded&#039;);&lt;br /&gt;
        list.removeAttribute(&#039;data-adaptive-limit&#039;);&lt;br /&gt;
        list.style.removeProperty(&#039;--adaptive-recent-h&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    items.forEach(function (item) {&lt;br /&gt;
        item.classList.remove(&#039;is-adaptive-hidden&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function resetLeftBillboardAdaptiveState() {&lt;br /&gt;
    var box = document.querySelector(&#039;#clbi-left-sidebar .left-billboard-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.remove(&#039;is-left-ad-title-only&#039;, &#039;is-left-ad-extended&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-h&#039;);&lt;br /&gt;
    box.style.removeProperty(&#039;--left-billboard-finish-h&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiShellVerticalScale() {&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    var host;&lt;br /&gt;
    var topInner;&lt;br /&gt;
    var bottomInner;&lt;br /&gt;
    var thresholdH;&lt;br /&gt;
    var gap;&lt;br /&gt;
    var outerGapTotal;&lt;br /&gt;
    var baseStageH;&lt;br /&gt;
    var stageW;&lt;br /&gt;
    var stageH;&lt;br /&gt;
    var viewportW;&lt;br /&gt;
    var viewportH;&lt;br /&gt;
    var availableW;&lt;br /&gt;
    var availableH;&lt;br /&gt;
    var topH;&lt;br /&gt;
    var bottomH;&lt;br /&gt;
    var contentH;&lt;br /&gt;
    var widthScale;&lt;br /&gt;
    var heightScale;&lt;br /&gt;
    var scale;&lt;br /&gt;
    var shouldScale;&lt;br /&gt;
&lt;br /&gt;
    if (!root || !body) return;&lt;br /&gt;
&lt;br /&gt;
    host = ensureClbiVerticalScaleHost();&lt;br /&gt;
    if (!host) return;&lt;br /&gt;
&lt;br /&gt;
    thresholdH = readClbiRootPx(&#039;--clbi-vertical-scale-threshold-h&#039;, 1080);&lt;br /&gt;
    gap = readClbiRootPx(&#039;--layout-gap&#039;, 8);&lt;br /&gt;
    outerGapTotal = gap * 2;&lt;br /&gt;
    baseStageH = Math.max(420, thresholdH - outerGapTotal);&lt;br /&gt;
    stageW = readClbiRootPx(&#039;--layout-shell-w&#039;, 1880);&lt;br /&gt;
&lt;br /&gt;
    viewportW = Math.max(320, window.innerWidth || document.documentElement.clientWidth || stageW);&lt;br /&gt;
    viewportH = Math.max(320, window.innerHeight || document.documentElement.clientHeight || thresholdH);&lt;br /&gt;
    availableW = Math.max(240, viewportW - outerGapTotal);&lt;br /&gt;
    availableH = Math.max(240, viewportH - outerGapTotal);&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    평상시에는 기존 세로 채움 레이아웃을 기준으로 삼는다.&lt;br /&gt;
    세로가 기준점보다 작아지는 경우에는 기준 높이를 고정점으로 삼고,&lt;br /&gt;
    가로만 부족한 경우에는 현재 사용 가능한 세로 높이를 고정점으로 삼는다.&lt;br /&gt;
    이렇게 해야 가로 부족으로 scale에 들어갈 때 본문 높이가 갑자기 접히지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    stageH = availableH &amp;gt;= baseStageH ? availableH : baseStageH;&lt;br /&gt;
&lt;br /&gt;
    topInner = document.getElementById(&#039;clbi-top-nav&#039;);&lt;br /&gt;
    bottomInner = document.getElementById(&#039;clbi-bottom-nav&#039;);&lt;br /&gt;
&lt;br /&gt;
    topH = topInner ? Math.ceil(topInner.offsetHeight || topInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    bottomH = bottomInner ? Math.ceil(bottomInner.offsetHeight || bottomInner.getBoundingClientRect().height || 38) : 38;&lt;br /&gt;
    contentH = Math.max(360, Math.floor(stageH - topH - bottomH - (gap * 2)));&lt;br /&gt;
&lt;br /&gt;
    widthScale = availableW &amp;lt; stageW ? availableW / stageW : 1;&lt;br /&gt;
    heightScale = availableH &amp;lt; baseStageH ? availableH / baseStageH : 1;&lt;br /&gt;
    scale = Math.min(1, widthScale, heightScale);&lt;br /&gt;
    scale = Math.max(0.50, Math.min(1, Math.floor(scale * 1000) / 1000));&lt;br /&gt;
    shouldScale = scale &amp;lt; 0.999;&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-w&#039;, stageW + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-design-h&#039;, Math.floor(stageH) + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-stage-content-h&#039;, contentH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-shell-scale&#039;, String(scale));&lt;br /&gt;
&lt;br /&gt;
    body.classList.toggle(&#039;clbi-shell-vertical-scale&#039;, shouldScale);&lt;br /&gt;
&lt;br /&gt;
    if (shouldScale) {&lt;br /&gt;
        resetLeftRecentAdaptiveState();&lt;br /&gt;
        resetLeftBillboardAdaptiveState();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
window.updateClbiShellVerticalScale = updateClbiShellVerticalScale;&lt;br /&gt;
&lt;br /&gt;
var $contentWrapper = $(&#039;.content-wrapper&#039;).first();&lt;br /&gt;
&lt;br /&gt;
if ($contentWrapper.length) {&lt;br /&gt;
    $(&#039;#clbi-top-nav-wrap, #clbi-bottom-nav-wrap&#039;).remove();&lt;br /&gt;
    $contentWrapper.before(buildClbiNavHtml(&#039;top&#039;));&lt;br /&gt;
    $contentWrapper.after(buildClbiNavHtml(&#039;bottom&#039;));&lt;br /&gt;
    renderClbiBottomShortcutGuide();&lt;br /&gt;
    initClbiNationsTabpanelControls(document);&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
function runClbiShellMetricsBatch() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var root = document.documentElement;&lt;br /&gt;
    var topH = 0;&lt;br /&gt;
    var bottomH = 0;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = null;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    if (top) {&lt;br /&gt;
        topH = Math.ceil(top.getBoundingClientRect().height || top.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (bottom) {&lt;br /&gt;
        bottomH = Math.ceil(bottom.getBoundingClientRect().height || bottom.offsetHeight || 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-top-nav-outer-h&#039;, topH + &#039;px&#039;);&lt;br /&gt;
    root.style.setProperty(&#039;--clbi-bottom-nav-outer-h&#039;, bottomH + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (typeof updateClbiShellVerticalScale === &#039;function&#039;) {&lt;br /&gt;
        updateClbiShellVerticalScale();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestClbiShellMetricsFrame() {&lt;br /&gt;
    if (CLBI_SHELL_METRICS_RAF !== null) return;&lt;br /&gt;
&lt;br /&gt;
    CLBI_SHELL_METRICS_RAF = window.requestAnimationFrame&lt;br /&gt;
        ? window.requestAnimationFrame(runClbiShellMetricsBatch)&lt;br /&gt;
        : window.setTimeout(runClbiShellMetricsBatch, 16);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiShellMetrics() {&lt;br /&gt;
    requestClbiShellMetricsFrame();&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 0);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 80);&lt;br /&gt;
    window.setTimeout(requestClbiShellMetricsFrame, 240);&lt;br /&gt;
}&lt;br /&gt;
function watchClbiShellMetrics() {&lt;br /&gt;
    var top = document.getElementById(&#039;clbi-top-nav-wrap&#039;);&lt;br /&gt;
    var bottom = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var observer;&lt;br /&gt;
&lt;br /&gt;
    scheduleClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
    $(window).on(&#039;resize orientationchange&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    $(window).on(&#039;pageshow.clbiShellScale focus.clbiShellScale&#039;, scheduleClbiShellMetrics);&lt;br /&gt;
    document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
        if (!document.hidden) scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
    $(window).on(&#039;resize.clbiLeftBillboard orientationchange.clbiLeftBillboard&#039;, scheduleLeftSidebarVerticalFit);&lt;br /&gt;
    $(window).on(&#039;resize.clbiRecentViewport orientationchange.clbiRecentViewport&#039;, function () { scheduleAdaptiveLeftRecentItems(); scheduleClbiContentBottomGap(); });&lt;br /&gt;
    $(window).on(&#039;resize.clbiContentBottomGap orientationchange.clbiContentBottomGap&#039;, scheduleClbiContentBottomGap);&lt;br /&gt;
&lt;br /&gt;
    if (window.ResizeObserver) {&lt;br /&gt;
        observer = new ResizeObserver(scheduleClbiShellMetrics);&lt;br /&gt;
        if (top) observer.observe(top);&lt;br /&gt;
        if (bottom) observer.observe(bottom);&lt;br /&gt;
        window.CLBI_SHELL_RESIZE_OBSERVER = observer;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindClbiWorldbuildingToggle(buttonSelector, menuSelector) {&lt;br /&gt;
    $(buttonSelector).on(&#039;click&#039;, function() {&lt;br /&gt;
        var $menu = $(menuSelector);&lt;br /&gt;
        var $btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        $menu.toggleClass(&#039;worldbuilding-open&#039;);&lt;br /&gt;
        $btn.toggleClass(&#039;clbi-tnav-active&#039;, $menu.hasClass(&#039;worldbuilding-open&#039;));&lt;br /&gt;
        scheduleClbiShellMetrics();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-tnav-worldbuilding&#039;, &#039;#clbi-sub-worldbuilding&#039;);&lt;br /&gt;
bindClbiWorldbuildingToggle(&#039;#clbi-bnav-worldbuilding&#039;, &#039;#clbi-bottom-sub-worldbuilding&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-top-search-input, #clbi-bottom-search-input&#039;).on(&#039;keydown&#039;, function(e) {&lt;br /&gt;
    if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
        var q = $(this).val().trim();&lt;br /&gt;
        if (q) window.location.href = &#039;/index.php?search=&#039; + encodeURIComponent(q);&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
if (window.mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
        initClbiBottomShortcutSystem($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
watchClbiShellMetrics();&lt;br /&gt;
&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// 페이지 전환 사운드&lt;br /&gt;
var transitionSound = new Audio(&#039;/index.php?title=특수:Redirect/file/Sfx-ui-001.mp3&#039;);&lt;br /&gt;
&lt;br /&gt;
(function() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
    transitionSound.volume = sfxOn ? master * sfx : 0;&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
function playStaticSound() {&lt;br /&gt;
    var master = parseFloat(localStorage.getItem(&#039;clbi-audio-master&#039;) || 80) / 100;&lt;br /&gt;
    var sfx = parseFloat(localStorage.getItem(&#039;clbi-audio-sfx&#039;) || 60) / 100;&lt;br /&gt;
    var sfxOn = localStorage.getItem(&#039;clbi-audio-sfxOn&#039;) !== &#039;false&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!sfxOn) return;&lt;br /&gt;
&lt;br /&gt;
    transitionSound.volume = master * sfx;&lt;br /&gt;
    transitionSound.currentTime = 0;&lt;br /&gt;
    transitionSound.play();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 현재 언어 감지&lt;br /&gt;
function getCurrentLang() {&lt;br /&gt;
    var langData = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    return langData ? (langData.getAttribute(&#039;data-lang&#039;) || &#039;ko&#039;) : &#039;ko&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizePageName(value) {&lt;br /&gt;
    return String(value || &#039;&#039;)&lt;br /&gt;
        .split(&#039;?&#039;)[0]&lt;br /&gt;
        .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildWikiPath(title) {&lt;br /&gt;
    return &#039;/index.php/&#039; + encodeURI(String(title || &#039;&#039;).replace(/ /g, &#039;_&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLangShortCode(lang) {&lt;br /&gt;
    var map = { ko: &#039;KR&#039;, en: &#039;EN&#039;, zh: &#039;ZH&#039;, ja: &#039;JA&#039;, ru: &#039;RU&#039;, es: &#039;ES&#039; };&lt;br /&gt;
    return map[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getLanguageTargetTitle(lang) {&lt;br /&gt;
    var data = document.getElementById(&#039;clbi-lang-data&#039;);&lt;br /&gt;
    if (!data || !lang) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var keys = [&lt;br /&gt;
        &#039;data-&#039; + lang,&lt;br /&gt;
        &#039;data-page-&#039; + lang,&lt;br /&gt;
        &#039;data-title-&#039; + lang,&lt;br /&gt;
        &#039;data-target-&#039; + lang,&lt;br /&gt;
        &#039;data-lang-&#039; + lang&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    for (var i = 0; i &amp;lt; keys.length; i++) {&lt;br /&gt;
        var value = data.getAttribute(keys[i]);&lt;br /&gt;
        if (value) return value;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function escapeClbiHtml(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var SIDEBAR_LANG_SVG_NS = &#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_TITLE = &#039;MediaWiki:LanguageStatus.json&#039;;&lt;br /&gt;
var SIDEBAR_LANGUAGE_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;English&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;Русский&#039;,&lt;br /&gt;
    es: &#039;Español&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_DIAL_LABELS = {&lt;br /&gt;
    ko: &#039;한국어&#039;,&lt;br /&gt;
    en: &#039;ENG&#039;,&lt;br /&gt;
    zh: &#039;中文&#039;,&lt;br /&gt;
    ja: &#039;日本語&#039;,&lt;br /&gt;
    ru: &#039;РУС&#039;,&lt;br /&gt;
    es: &#039;ESP&#039;&lt;br /&gt;
};&lt;br /&gt;
var SIDEBAR_LANGUAGE_STATUS_VALUES = {&lt;br /&gt;
    available: true,&lt;br /&gt;
    wip: true,&lt;br /&gt;
    unavailable: true&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageStatusRegistry = {};&lt;br /&gt;
var sidebarLanguageStatusLoaded = false;&lt;br /&gt;
var sidebarLanguageStatusLoading = false;&lt;br /&gt;
var sidebarLanguageStatusCallbacks = [];&lt;br /&gt;
&lt;br /&gt;
var sidebarLanguageState = {&lt;br /&gt;
    order: [&#039;ko&#039;, &#039;en&#039;, &#039;zh&#039;, &#039;ja&#039;, &#039;ru&#039;, &#039;es&#039;],&lt;br /&gt;
    currentLang: &#039;ko&#039;,&lt;br /&gt;
    baseIndex: 0,&lt;br /&gt;
    selectedIndex: 0,&lt;br /&gt;
    rotation: 0,&lt;br /&gt;
    dragging: false,&lt;br /&gt;
    dragMoved: false,&lt;br /&gt;
    dragStartX: 0,&lt;br /&gt;
    dragStartY: 0,&lt;br /&gt;
    dragStartRotation: 0,&lt;br /&gt;
    dragAxis: null,&lt;br /&gt;
    pointerCaptured: false,&lt;br /&gt;
    lastX: 0,&lt;br /&gt;
    lastTime: 0,&lt;br /&gt;
    releaseVelocity: 0,&lt;br /&gt;
    suppressClickUntil: 0,&lt;br /&gt;
    raf: null,&lt;br /&gt;
    pendingRotation: 0,&lt;br /&gt;
    snapTimer: null,&lt;br /&gt;
    inertiaRaf: null,&lt;br /&gt;
    navigateTimer: null,&lt;br /&gt;
    bound: false,&lt;br /&gt;
    boundElement: null,&lt;br /&gt;
    rotor: null,&lt;br /&gt;
    cx: 101,&lt;br /&gt;
    cy: 119,&lt;br /&gt;
    outerR: 109,&lt;br /&gt;
    innerR: 28,&lt;br /&gt;
    sectorAngle: 30,&lt;br /&gt;
    halfSector: 15,&lt;br /&gt;
    repeats: 8,&lt;br /&gt;
    dragSensitivity: 0.58,&lt;br /&gt;
    maxSpinVelocity: 1.75,&lt;br /&gt;
    minSpinVelocity: 0.055,&lt;br /&gt;
    spinDecel: 0.00185&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function createSidebarLanguageSvgEl(tag) {&lt;br /&gt;
    return document.createElementNS(SIDEBAR_LANG_SVG_NS, tag);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageIndex(index) {&lt;br /&gt;
    var length = sidebarLanguageState.order.length;&lt;br /&gt;
    var normalized = index % length;&lt;br /&gt;
    return normalized &amp;lt; 0 ? normalized + length : normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || String(lang || &#039;&#039;).toUpperCase();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageDialName(lang) {&lt;br /&gt;
    return SIDEBAR_LANGUAGE_DIAL_LABELS[lang] || getSidebarLanguageName(lang);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeSidebarLanguageStatusValue(value) {&lt;br /&gt;
    value = String(value == null ? &#039;&#039; : value).toLowerCase().trim();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_STATUS_VALUES[value] ? value : &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusPageKey() {&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
&lt;br /&gt;
    return normalized || raw || &#039;대문&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusEntry() {&lt;br /&gt;
    var registry = sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    var pages = registry.pages &amp;amp;&amp;amp; typeof registry.pages === &#039;object&#039; ? registry.pages : registry;&lt;br /&gt;
    var raw = String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var normalized = normalizePageName(raw);&lt;br /&gt;
    var title = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).trim();&lt;br /&gt;
    var keys = [&lt;br /&gt;
        normalized,&lt;br /&gt;
        raw,&lt;br /&gt;
        raw.replace(/_/g, &#039; &#039;),&lt;br /&gt;
        normalized.replace(/ /g, &#039;_&#039;),&lt;br /&gt;
        title,&lt;br /&gt;
        title.replace(/_/g, &#039; &#039;)&lt;br /&gt;
    ];&lt;br /&gt;
    var i;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; keys.length; i += 1) {&lt;br /&gt;
        if (keys[i] &amp;amp;&amp;amp; pages[keys[i]] &amp;amp;&amp;amp; typeof pages[keys[i]] === &#039;object&#039;) {&lt;br /&gt;
            return pages[keys[i]];&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {};&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatusOverride(lang) {&lt;br /&gt;
    var entry = getSidebarLanguageStatusEntry();&lt;br /&gt;
    return normalizeSidebarLanguageStatusValue(entry[lang]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function flushSidebarLanguageStatusCallbacks() {&lt;br /&gt;
    var callbacks = sidebarLanguageStatusCallbacks.slice();&lt;br /&gt;
    sidebarLanguageStatusCallbacks.length = 0;&lt;br /&gt;
&lt;br /&gt;
    callbacks.forEach(function(callback) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
            callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadSidebarLanguageStatusRegistry(callback, force) {&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        sidebarLanguageStatusCallbacks.push(callback);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoaded &amp;amp;&amp;amp; !force) {&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sidebarLanguageStatusLoading) return;&lt;br /&gt;
&lt;br /&gt;
    sidebarLanguageStatusLoading = true;&lt;br /&gt;
&lt;br /&gt;
    function finishLanguageStatus(parsed) {&lt;br /&gt;
        sidebarLanguageStatusRegistry = parsed &amp;amp;&amp;amp; typeof parsed === &#039;object&#039; ? parsed : {};&lt;br /&gt;
        sidebarLanguageStatusLoaded = true;&lt;br /&gt;
        sidebarLanguageStatusLoading = false;&lt;br /&gt;
        flushSidebarLanguageStatusCallbacks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!force &amp;amp;&amp;amp; window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.fetchJsonRef === &#039;function&#039;) {&lt;br /&gt;
        window.EntryStore.fetchJsonRef(SIDEBAR_LANGUAGE_STATUS_TITLE, { noStore: false })&lt;br /&gt;
            .then(function (parsed) { finishLanguageStatus(parsed); })&lt;br /&gt;
            .catch(function () { finishLanguageStatus({}); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    (function () {&lt;br /&gt;
        var url = mw.util.getUrl(SIDEBAR_LANGUAGE_STATUS_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        if (window.RevisionManifest &amp;amp;&amp;amp; typeof window.RevisionManifest.addRevisionParam === &#039;function&#039;) {&lt;br /&gt;
            url = window.RevisionManifest.addRevisionParam(url, SIDEBAR_LANGUAGE_STATUS_TITLE);&lt;br /&gt;
        }&lt;br /&gt;
        $.ajax({&lt;br /&gt;
            url: url,&lt;br /&gt;
            dataType: &#039;text&#039;,&lt;br /&gt;
            cache: true&lt;br /&gt;
        }).done(function(text) {&lt;br /&gt;
            var parsed = {};&lt;br /&gt;
&lt;br /&gt;
            try {&lt;br /&gt;
                parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                console.error(&#039;LanguageStatus.json parse failed:&#039;, err);&lt;br /&gt;
                parsed = {};&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            finishLanguageStatus(parsed);&lt;br /&gt;
        }).fail(function() {&lt;br /&gt;
            finishLanguageStatus({});&lt;br /&gt;
        });&lt;br /&gt;
    })();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
window.CLBI_LANGUAGE_STATUS = {&lt;br /&gt;
    title: SIDEBAR_LANGUAGE_STATUS_TITLE,&lt;br /&gt;
    languages: sidebarLanguageState.order.slice(),&lt;br /&gt;
    labels: SIDEBAR_LANGUAGE_LABELS,&lt;br /&gt;
    dialLabels: SIDEBAR_LANGUAGE_DIAL_LABELS,&lt;br /&gt;
    getPageKey: getSidebarLanguageStatusPageKey,&lt;br /&gt;
    getRegistry: function() {&lt;br /&gt;
        return sidebarLanguageStatusRegistry || {};&lt;br /&gt;
    },&lt;br /&gt;
    reload: function(callback) {&lt;br /&gt;
        sidebarLanguageStatusLoaded = false;&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            renderSidebarLanguageBox();&lt;br /&gt;
            if (typeof callback === &#039;function&#039;) callback(sidebarLanguageStatusRegistry);&lt;br /&gt;
        }, true);&lt;br /&gt;
    },&lt;br /&gt;
    refreshDial: function() {&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageMeta(lang) {&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var targetTitle = getLanguageTargetTitle(lang);&lt;br /&gt;
    var isCurrent = lang === currentLang;&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        lang: lang,&lt;br /&gt;
        code: getLangShortCode(lang),&lt;br /&gt;
        name: getSidebarLanguageName(lang),&lt;br /&gt;
        dialName: getSidebarLanguageDialName(lang),&lt;br /&gt;
        targetTitle: targetTitle,&lt;br /&gt;
        isCurrent: isCurrent,&lt;br /&gt;
        canMove: !!targetTitle &amp;amp;&amp;amp; !isCurrent&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageStatus(meta) {&lt;br /&gt;
    var override;&lt;br /&gt;
&lt;br /&gt;
    if (!meta) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (meta.isCurrent) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-current&#039;,&lt;br /&gt;
            label: &#039;CURRENT&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    override = getSidebarLanguageStatusOverride(meta.lang);&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;wip&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;WIP&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;unavailable&#039;) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: &#039;is-locked&#039;,&lt;br /&gt;
            label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: false&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (override === &#039;available&#039; || meta.targetTitle) {&lt;br /&gt;
        return {&lt;br /&gt;
            className: meta.targetTitle ? &#039;is-ready&#039; : &#039;is-locked&#039;,&lt;br /&gt;
            label: meta.targetTitle ? &#039;AVAILABLE&#039; : &#039;UNAVAILABLE&#039;,&lt;br /&gt;
            canApply: !!meta.targetTitle&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        className: &#039;is-locked&#039;,&lt;br /&gt;
        label: &#039;UNAVAILABLE&#039;,&lt;br /&gt;
        canApply: false&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguageRad(deg) {&lt;br /&gt;
    return (deg * Math.PI) / 180;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function sidebarLanguagePointAt(radius, deg) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var angle = sidebarLanguageRad(deg);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        x: state.cx + Math.sin(angle) * radius,&lt;br /&gt;
        y: state.cy - Math.cos(angle) * radius&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageSectorPath(start, end) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var p1 = sidebarLanguagePointAt(state.outerR, start);&lt;br /&gt;
    var p2 = sidebarLanguagePointAt(state.outerR, end);&lt;br /&gt;
    var p3 = sidebarLanguagePointAt(state.innerR, end);&lt;br /&gt;
    var p4 = sidebarLanguagePointAt(state.innerR, start);&lt;br /&gt;
    var largeArc = Math.abs(end - start) &amp;gt; 180 ? 1 : 0;&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;M&#039;, p1.x.toFixed(3), p1.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.outerR, state.outerR, 0, largeArc, 1, p2.x.toFixed(3), p2.y.toFixed(3),&lt;br /&gt;
        &#039;L&#039;, p3.x.toFixed(3), p3.y.toFixed(3),&lt;br /&gt;
        &#039;A&#039;, state.innerR, state.innerR, 0, largeArc, 0, p4.x.toFixed(3), p4.y.toFixed(3),&lt;br /&gt;
        &#039;Z&#039;&lt;br /&gt;
    ].join(&#039; &#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageShellPath() {&lt;br /&gt;
    return getSidebarLanguageSectorPath(-68, 68);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguageByStep(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        index: index,&lt;br /&gt;
        meta: getSidebarLanguageMeta(state.order[index])&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewIndex() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    return normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getSidebarLanguagePreviewMeta() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    return getSidebarLanguageMeta(state.order[getSidebarLanguagePreviewIndex()]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeSidebarLanguageSector(step) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var item = getSidebarLanguageByStep(step);&lt;br /&gt;
    var group = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    var path = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    var label = createSidebarLanguageSvgEl(&#039;text&#039;);&lt;br /&gt;
    var labelY = state.cy - 78;&lt;br /&gt;
    var angle = step * state.sectorAngle;&lt;br /&gt;
&lt;br /&gt;
    group.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-group&#039;);&lt;br /&gt;
    group.setAttribute(&#039;data-step&#039;, String(step));&lt;br /&gt;
    group.setAttribute(&#039;data-index&#039;, String(item.index));&lt;br /&gt;
    group.setAttribute(&#039;data-lang&#039;, item.meta.lang);&lt;br /&gt;
    group.setAttribute(&#039;transform&#039;, &#039;rotate(&#039; + angle + &#039; &#039; + state.cx + &#039; &#039; + state.cy + &#039;)&#039;);&lt;br /&gt;
&lt;br /&gt;
    path.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector&#039;);&lt;br /&gt;
    path.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
&lt;br /&gt;
    label.setAttribute(&#039;class&#039;, &#039;sidebar-lang-sector-label&#039;);&lt;br /&gt;
    label.setAttribute(&#039;x&#039;, String(state.cx));&lt;br /&gt;
    label.setAttribute(&#039;y&#039;, String(labelY + 5));&lt;br /&gt;
    label.textContent = item.meta.dialName || item.meta.name;&lt;br /&gt;
&lt;br /&gt;
    group.appendChild(path);&lt;br /&gt;
    group.appendChild(label);&lt;br /&gt;
&lt;br /&gt;
    group.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        if (sidebarLanguageState.dragging || performance.now() &amp;lt; sidebarLanguageState.suppressClickUntil) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        snapSidebarLanguageToStep(parseInt(group.getAttribute(&#039;data-step&#039;) || &#039;0&#039;, 10), true);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return group;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageWheel() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var svg;&lt;br /&gt;
    var defs;&lt;br /&gt;
    var clip;&lt;br /&gt;
    var clipPath;&lt;br /&gt;
    var shadowBlur;&lt;br /&gt;
    var blur;&lt;br /&gt;
    var fixedDepthGradient;&lt;br /&gt;
    var shell;&lt;br /&gt;
    var clipped;&lt;br /&gt;
    var rotor;&lt;br /&gt;
    var fixedDepthPath;&lt;br /&gt;
    var fixedFocus;&lt;br /&gt;
    var shadowSoft;&lt;br /&gt;
    var shadowHard;&lt;br /&gt;
    var rim;&lt;br /&gt;
    var pointer;&lt;br /&gt;
    var tri;&lt;br /&gt;
    var line;&lt;br /&gt;
    var step;&lt;br /&gt;
&lt;br /&gt;
    if (!fan) return;&lt;br /&gt;
&lt;br /&gt;
    fan.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    svg = createSidebarLanguageSvgEl(&#039;svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fan-svg&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;viewBox&#039;, &#039;0 0 202 150&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;preserveAspectRatio&#039;, &#039;xMidYMid meet&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;role&#039;, &#039;img&#039;);&lt;br /&gt;
    svg.setAttribute(&#039;aria-label&#039;, &#039;언어 선택 다이얼&#039;);&lt;br /&gt;
&lt;br /&gt;
    defs = createSidebarLanguageSvgEl(&#039;defs&#039;);&lt;br /&gt;
&lt;br /&gt;
    clip = createSidebarLanguageSvgEl(&#039;clipPath&#039;);&lt;br /&gt;
    clip.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fan-clip&#039;);&lt;br /&gt;
    clipPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    clipPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    clip.appendChild(clipPath);&lt;br /&gt;
&lt;br /&gt;
    shadowBlur = createSidebarLanguageSvgEl(&#039;filter&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-shadow-blur&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;x&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;y&#039;, &#039;-20%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;width&#039;, &#039;140%&#039;);&lt;br /&gt;
    shadowBlur.setAttribute(&#039;height&#039;, &#039;140%&#039;);&lt;br /&gt;
    blur = createSidebarLanguageSvgEl(&#039;feGaussianBlur&#039;);&lt;br /&gt;
    blur.setAttribute(&#039;stdDeviation&#039;, &#039;3&#039;);&lt;br /&gt;
    shadowBlur.appendChild(blur);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthGradient = createSidebarLanguageSvgEl(&#039;linearGradient&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-language-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y1&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;x2&#039;, &#039;0&#039;);&lt;br /&gt;
    fixedDepthGradient.setAttribute(&#039;y2&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    [&lt;br /&gt;
        [&#039;0%&#039;, &#039;#ffffff&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;34%&#039;, &#039;#ffffff&#039;, &#039;0.006&#039;],&lt;br /&gt;
        [&#039;58%&#039;, &#039;#000000&#039;, &#039;0.030&#039;],&lt;br /&gt;
        [&#039;100%&#039;, &#039;#000000&#039;, &#039;0.250&#039;]&lt;br /&gt;
    ].forEach(function(item) {&lt;br /&gt;
        var stop = createSidebarLanguageSvgEl(&#039;stop&#039;);&lt;br /&gt;
        stop.setAttribute(&#039;offset&#039;, item[0]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-color&#039;, item[1]);&lt;br /&gt;
        stop.setAttribute(&#039;stop-opacity&#039;, item[2]);&lt;br /&gt;
        fixedDepthGradient.appendChild(stop);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    defs.appendChild(clip);&lt;br /&gt;
    defs.appendChild(shadowBlur);&lt;br /&gt;
    defs.appendChild(fixedDepthGradient);&lt;br /&gt;
    svg.appendChild(defs);&lt;br /&gt;
&lt;br /&gt;
    shell = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;class&#039;, &#039;sidebar-lang-shell&#039;);&lt;br /&gt;
    shell.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shell);&lt;br /&gt;
&lt;br /&gt;
    clipped = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    clipped.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    rotor = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;id&#039;, &#039;clbi-sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
    rotor.setAttribute(&#039;class&#039;, &#039;sidebar-lang-wheel-rotor&#039;);&lt;br /&gt;
&lt;br /&gt;
    for (step = -state.repeats; step &amp;lt;= state.repeats; step += 1) {&lt;br /&gt;
        rotor.appendChild(makeSidebarLanguageSector(step));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    clipped.appendChild(rotor);&lt;br /&gt;
    svg.appendChild(clipped);&lt;br /&gt;
&lt;br /&gt;
    fixedDepthPath = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-depth&#039;);&lt;br /&gt;
    fixedDepthPath.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(fixedDepthPath);&lt;br /&gt;
&lt;br /&gt;
    fixedFocus = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-focus&#039;);&lt;br /&gt;
    fixedFocus.setAttribute(&#039;d&#039;, getSidebarLanguageSectorPath(-state.halfSector, state.halfSector));&lt;br /&gt;
    svg.appendChild(fixedFocus);&lt;br /&gt;
&lt;br /&gt;
    shadowSoft = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-soft&#039;);&lt;br /&gt;
    shadowSoft.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowSoft);&lt;br /&gt;
&lt;br /&gt;
    shadowHard = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;class&#039;, &#039;sidebar-lang-inner-shadow-hard&#039;);&lt;br /&gt;
    shadowHard.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(shadowHard);&lt;br /&gt;
&lt;br /&gt;
    rim = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;class&#039;, &#039;sidebar-lang-rim&#039;);&lt;br /&gt;
    rim.setAttribute(&#039;d&#039;, getSidebarLanguageShellPath());&lt;br /&gt;
    svg.appendChild(rim);&lt;br /&gt;
&lt;br /&gt;
    pointer = createSidebarLanguageSvgEl(&#039;g&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;class&#039;, &#039;sidebar-lang-fixed-pointer&#039;);&lt;br /&gt;
    pointer.setAttribute(&#039;clip-path&#039;, &#039;url(#clbi-sidebar-language-fan-clip)&#039;);&lt;br /&gt;
&lt;br /&gt;
    tri = createSidebarLanguageSvgEl(&#039;path&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-triangle&#039;);&lt;br /&gt;
    tri.setAttribute(&#039;d&#039;, &#039;M &#039; + (state.cx - 10) + &#039; 10 L &#039; + (state.cx + 10) + &#039; 10 L &#039; + state.cx + &#039; 26 Z&#039;);&lt;br /&gt;
    pointer.appendChild(tri);&lt;br /&gt;
&lt;br /&gt;
    line = createSidebarLanguageSvgEl(&#039;line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;class&#039;, &#039;sidebar-lang-pointer-line&#039;);&lt;br /&gt;
    line.setAttribute(&#039;x1&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;x2&#039;, String(state.cx));&lt;br /&gt;
    line.setAttribute(&#039;y1&#039;, &#039;24&#039;);&lt;br /&gt;
    line.setAttribute(&#039;y2&#039;, &#039;112&#039;);&lt;br /&gt;
    pointer.appendChild(line);&lt;br /&gt;
&lt;br /&gt;
    svg.appendChild(pointer);&lt;br /&gt;
    fan.appendChild(svg);&lt;br /&gt;
&lt;br /&gt;
    state.rotor = rotor;&lt;br /&gt;
    setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateSidebarLanguageDial() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var meta = getSidebarLanguagePreviewMeta();&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
    var selectedValue = document.getElementById(&#039;clbi-sidebar-lang-selected-value&#039;);&lt;br /&gt;
    var availabilityPanel = document.getElementById(&#039;clbi-sidebar-lang-availability-panel&#039;);&lt;br /&gt;
    var availabilityValue = document.getElementById(&#039;clbi-sidebar-lang-availability-value&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (selectedValue) {&lt;br /&gt;
        selectedValue.textContent = meta.name;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityPanel) {&lt;br /&gt;
        availabilityPanel.classList.remove(&#039;is-ready&#039;, &#039;is-current&#039;, &#039;is-locked&#039;);&lt;br /&gt;
        availabilityPanel.classList.add(status.className);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (availabilityValue) {&lt;br /&gt;
        availabilityValue.textContent = status.label;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (apply) {&lt;br /&gt;
        apply.classList.toggle(&#039;is-disabled&#039;, !status.canApply);&lt;br /&gt;
        apply.setAttribute(&#039;aria-disabled&#039;, status.canApply ? &#039;false&#039; : &#039;true&#039;);&lt;br /&gt;
        apply.setAttribute(&#039;aria-label&#039;, status.canApply ? (meta.name + &#039; 적용&#039;) : (meta.isCurrent ? &#039;현재 언어&#039; : &#039;사용할 수 없는 언어&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (selector) {&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-lang&#039;, meta.lang);&lt;br /&gt;
        selector.setAttribute(&#039;data-selected-code&#039;, meta.code);&lt;br /&gt;
        selector.classList.toggle(&#039;is-current&#039;, meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-ready&#039;, status.canApply);&lt;br /&gt;
        selector.classList.toggle(&#039;is-locked&#039;, !status.canApply &amp;amp;&amp;amp; !meta.isCurrent);&lt;br /&gt;
        selector.classList.toggle(&#039;is-dragging&#039;, !!state.dragging);&lt;br /&gt;
        selector.classList.toggle(&#039;is-spinning&#039;, !!state.inertiaRaf);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        meta: meta,&lt;br /&gt;
        status: status&lt;br /&gt;
    };&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageRotation(value, animate) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.rotation = value;&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (!state.rotor) return;&lt;br /&gt;
&lt;br /&gt;
    if (animate) {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-snapping&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.rotor.style.transform = &#039;rotate(&#039; + state.rotation.toFixed(3) + &#039;deg)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function requestSidebarLanguageRotation(value) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    state.pendingRotation = value;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) return;&lt;br /&gt;
&lt;br /&gt;
    state.raf = requestAnimationFrame(function() {&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
        setSidebarLanguageRotation(state.pendingRotation, false);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function cancelSidebarLanguageSpin() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    if (state.inertiaRaf) {&lt;br /&gt;
        cancelAnimationFrame(state.inertiaRaf);&lt;br /&gt;
        state.inertiaRaf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finishSidebarLanguageSnap(nearestIndex, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
&lt;br /&gt;
    state.baseIndex = normalizeSidebarLanguageIndex(nearestIndex);&lt;br /&gt;
    state.selectedIndex = state.baseIndex;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-snapping is-dragging is-spinning&#039;);&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
&lt;br /&gt;
    if (typeof callback === &#039;function&#039;) {&lt;br /&gt;
        callback(getSidebarLanguageMeta(state.order[state.selectedIndex]));&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageToStep(step, animate, callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var targetRotation = -step * state.sectorAngle;&lt;br /&gt;
    var nearestIndex = normalizeSidebarLanguageIndex(state.baseIndex + step);&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.selectedIndex = nearestIndex;&lt;br /&gt;
    setSidebarLanguageRotation(targetRotation, !!animate);&lt;br /&gt;
&lt;br /&gt;
    state.snapTimer = setTimeout(function() {&lt;br /&gt;
        finishSidebarLanguageSnap(nearestIndex, callback);&lt;br /&gt;
    }, animate ? 230 : 0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function snapSidebarLanguageNearest(callback) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var step = Math.round(-state.rotation / state.sectorAngle);&lt;br /&gt;
    snapSidebarLanguageToStep(step, true, callback);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function startSidebarLanguageInertiaSpin(initialVelocity) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var velocity;&lt;br /&gt;
    var lastFrame;&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
&lt;br /&gt;
    velocity = Math.max(-state.maxSpinVelocity, Math.min(state.maxSpinVelocity, initialVelocity));&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(velocity) &amp;lt; state.minSpinVelocity) {&lt;br /&gt;
        snapSidebarLanguageNearest();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-sidebar-lang-selector&#039;).addClass(&#039;is-spinning&#039;);&lt;br /&gt;
    lastFrame = performance.now();&lt;br /&gt;
&lt;br /&gt;
    function frame(now) {&lt;br /&gt;
        var dt = Math.min(34, Math.max(1, now - lastFrame));&lt;br /&gt;
        var sign = velocity &amp;lt; 0 ? -1 : 1;&lt;br /&gt;
        var nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        lastFrame = now;&lt;br /&gt;
        state.rotation += velocity * dt;&lt;br /&gt;
        setSidebarLanguageRotation(state.rotation, false);&lt;br /&gt;
&lt;br /&gt;
        nextSpeed = Math.max(0, Math.abs(velocity) - (state.spinDecel * dt));&lt;br /&gt;
        velocity = sign * nextSpeed;&lt;br /&gt;
&lt;br /&gt;
        if (nextSpeed &amp;lt;= state.minSpinVelocity) {&lt;br /&gt;
            state.inertiaRaf = null;&lt;br /&gt;
            $(&#039;#clbi-sidebar-lang-selector&#039;).removeClass(&#039;is-spinning&#039;);&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    state.inertiaRaf = requestAnimationFrame(frame);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleSidebarLanguageNavigation(meta) {&lt;br /&gt;
    var status = getSidebarLanguageStatus(meta);&lt;br /&gt;
&lt;br /&gt;
    if (!meta || !status.canApply) return;&lt;br /&gt;
&lt;br /&gt;
    clearTimeout(sidebarLanguageState.navigateTimer);&lt;br /&gt;
    sidebarLanguageState.navigateTimer = setTimeout(function() {&lt;br /&gt;
        var title = getLanguageTargetTitle(meta.lang);&lt;br /&gt;
&lt;br /&gt;
        if (!title || meta.lang === getCurrentLang()) return;&lt;br /&gt;
&lt;br /&gt;
        window.location.href = buildWikiPath(title);&lt;br /&gt;
    }, 70);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setSidebarLanguageSelection(lang) {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var index = state.order.indexOf(lang);&lt;br /&gt;
&lt;br /&gt;
    if (index &amp;lt; 0) index = state.order.indexOf(getCurrentLang());&lt;br /&gt;
    if (index &amp;lt; 0) index = 0;&lt;br /&gt;
&lt;br /&gt;
    if (state.raf) {&lt;br /&gt;
        cancelAnimationFrame(state.raf);&lt;br /&gt;
        state.raf = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cancelSidebarLanguageSpin();&lt;br /&gt;
    clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
    state.currentLang = lang;&lt;br /&gt;
    state.baseIndex = index;&lt;br /&gt;
    state.selectedIndex = index;&lt;br /&gt;
    state.rotation = 0;&lt;br /&gt;
    state.dragging = false;&lt;br /&gt;
    state.dragMoved = false;&lt;br /&gt;
    state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
    renderSidebarLanguageWheel();&lt;br /&gt;
    updateSidebarLanguageDial();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveSidebarLanguageSelection(delta) {&lt;br /&gt;
    snapSidebarLanguageToStep(-delta, true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindSidebarLanguageSelector() {&lt;br /&gt;
    var state = sidebarLanguageState;&lt;br /&gt;
    var selector = document.getElementById(&#039;clbi-sidebar-lang-selector&#039;);&lt;br /&gt;
    var fan = document.getElementById(&#039;clbi-sidebar-lang-fan&#039;);&lt;br /&gt;
    var apply = document.getElementById(&#039;clbi-sidebar-lang-apply&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!selector || !fan || !apply) return;&lt;br /&gt;
&lt;br /&gt;
    if (state.bound &amp;amp;&amp;amp; state.boundElement === selector) return;&lt;br /&gt;
&lt;br /&gt;
    state.bound = true;&lt;br /&gt;
    state.boundElement = selector;&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerdown&#039;, function(e) {&lt;br /&gt;
        cancelSidebarLanguageSpin();&lt;br /&gt;
        clearTimeout(state.snapTimer);&lt;br /&gt;
&lt;br /&gt;
        state.dragging = true;&lt;br /&gt;
        state.dragMoved = false;&lt;br /&gt;
        state.dragStartX = e.clientX;&lt;br /&gt;
        state.dragStartY = e.clientY || 0;&lt;br /&gt;
        state.dragStartRotation = state.rotation;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = performance.now();&lt;br /&gt;
        state.releaseVelocity = 0;&lt;br /&gt;
&lt;br /&gt;
        selector.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
        selector.classList.remove(&#039;is-snapping&#039;);&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        Vertical page scrolling must stay available when the pointer starts on&lt;br /&gt;
        the language dial. Capture and preventDefault are delayed until a&lt;br /&gt;
        horizontal drag is confirmed.&lt;br /&gt;
        */&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointermove&#039;, function(e) {&lt;br /&gt;
        var now;&lt;br /&gt;
        var totalDx;&lt;br /&gt;
        var totalDy;&lt;br /&gt;
        var frameDx;&lt;br /&gt;
        var dt;&lt;br /&gt;
        var instantVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        totalDx = e.clientX - state.dragStartX;&lt;br /&gt;
        totalDy = (e.clientY || 0) - state.dragStartY;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragAxis &amp;amp;&amp;amp; (Math.abs(totalDx) &amp;gt; 4 || Math.abs(totalDy) &amp;gt; 4)) {&lt;br /&gt;
            state.dragAxis = Math.abs(totalDx) &amp;gt;= Math.abs(totalDy) ? &#039;x&#039; : &#039;y&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (state.dragAxis === &#039;y&#039;) {&lt;br /&gt;
                state.dragging = false;&lt;br /&gt;
                state.dragMoved = false;&lt;br /&gt;
                state.dragAxis = null;&lt;br /&gt;
                state.pointerCaptured = false;&lt;br /&gt;
                selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (fan.setPointerCapture &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
                try {&lt;br /&gt;
                    fan.setPointerCapture(e.pointerId);&lt;br /&gt;
                    state.pointerCaptured = true;&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    state.pointerCaptured = false;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragAxis !== &#039;x&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        now = performance.now();&lt;br /&gt;
        frameDx = e.clientX - state.lastX;&lt;br /&gt;
        dt = Math.max(1, now - state.lastTime);&lt;br /&gt;
&lt;br /&gt;
        if (Math.abs(totalDx) &amp;gt; 3) state.dragMoved = true;&lt;br /&gt;
&lt;br /&gt;
        instantVelocity = (frameDx * state.dragSensitivity) / dt;&lt;br /&gt;
        state.releaseVelocity = (state.releaseVelocity * 0.62) + (instantVelocity * 0.38);&lt;br /&gt;
        state.lastX = e.clientX;&lt;br /&gt;
        state.lastTime = now;&lt;br /&gt;
&lt;br /&gt;
        requestSidebarLanguageRotation(state.dragStartRotation + totalDx * state.dragSensitivity);&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    function finishDrag(e) {&lt;br /&gt;
        var velocityAge;&lt;br /&gt;
        var throwVelocity;&lt;br /&gt;
        var wasHorizontal;&lt;br /&gt;
&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        wasHorizontal = state.dragAxis === &#039;x&#039;;&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (fan.releasePointerCapture &amp;amp;&amp;amp; state.pointerCaptured &amp;amp;&amp;amp; e &amp;amp;&amp;amp; e.pointerId != null) {&lt;br /&gt;
            try { fan.releasePointerCapture(e.pointerId); } catch (err) {}&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
&lt;br /&gt;
        if (!wasHorizontal &amp;amp;&amp;amp; !state.dragMoved) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        velocityAge = performance.now() - state.lastTime;&lt;br /&gt;
        throwVelocity = velocityAge &amp;gt; 120 ? 0 : state.releaseVelocity;&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(throwVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            startSidebarLanguageInertiaSpin(throwVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fan.addEventListener(&#039;pointerup&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;pointercancel&#039;, finishDrag);&lt;br /&gt;
    fan.addEventListener(&#039;lostpointercapture&#039;, function() {&lt;br /&gt;
        if (!state.dragging) return;&lt;br /&gt;
&lt;br /&gt;
        state.dragging = false;&lt;br /&gt;
        state.pointerCaptured = false;&lt;br /&gt;
        state.dragAxis = null;&lt;br /&gt;
        selector.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (state.dragMoved &amp;amp;&amp;amp; Math.abs(state.releaseVelocity) &amp;gt;= state.minSpinVelocity) {&lt;br /&gt;
            state.suppressClickUntil = performance.now() + 180;&lt;br /&gt;
            startSidebarLanguageInertiaSpin(state.releaseVelocity);&lt;br /&gt;
        } else {&lt;br /&gt;
            snapSidebarLanguageNearest();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    apply.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
        snapSidebarLanguageNearest(function(meta) {&lt;br /&gt;
            scheduleSidebarLanguageNavigation(meta);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    selector.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
        if (e.key === &#039;ArrowLeft&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(-1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;ArrowRight&#039;) {&lt;br /&gt;
            moveSidebarLanguageSelection(1);&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (e.key === &#039;Enter&#039; || e.key === &#039; &#039;) {&lt;br /&gt;
            apply.click();&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderSidebarLanguageBox() {&lt;br /&gt;
    bindSidebarLanguageSelector();&lt;br /&gt;
    setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
&lt;br /&gt;
    if (!sidebarLanguageStatusLoaded) {&lt;br /&gt;
        loadSidebarLanguageStatusRegistry(function() {&lt;br /&gt;
            setSidebarLanguageSelection(getCurrentLang());&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadRecentChangesList(targetSelector, limit) {&lt;br /&gt;
    var $target = $(targetSelector);&lt;br /&gt;
&lt;br /&gt;
    if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
    var isNewsList = $target.closest(&#039;.clbi-left-news-box&#039;).length &amp;gt; 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.html((t &amp;amp;&amp;amp; t.loading) ? t.loading : &#039;불러오는 중...&#039;);&lt;br /&gt;
&lt;br /&gt;
    $.getJSON(&lt;br /&gt;
        &#039;/api.php?action=query&amp;amp;list=recentchanges&amp;amp;rclimit=&#039; + encodeURIComponent(limit || 5) + &#039;&amp;amp;rcprop=title|timestamp|user&amp;amp;format=json&amp;amp;rcnamespace=0&amp;amp;rctype=edit|new&#039;,&lt;br /&gt;
        function(data) {&lt;br /&gt;
            var items = data &amp;amp;&amp;amp; data.query ? data.query.recentchanges : [];&lt;br /&gt;
            var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (!items || !items.length) {&lt;br /&gt;
                $target.html(&#039;표시할 변경 사항이 없습니다.&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $.each(items, function(i, item) {&lt;br /&gt;
                var label = timeAgo(item.timestamp);&lt;br /&gt;
                var title = item.title || &#039;&#039;;&lt;br /&gt;
                var userName = item.user || &#039;Unknown&#039;;&lt;br /&gt;
                var pageHref = buildWikiPath(title);&lt;br /&gt;
                var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(userName) + &#039;.png&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (isNewsList) {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;news-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;news-recent-avatar&amp;quot; src=&amp;quot;&#039; + escapeHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;news-recent-main&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;div class=&amp;quot;news-recent-meta&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                    &#039;&amp;lt;span class=&amp;quot;news-recent-user&amp;quot;&amp;gt;@&#039; + escapeHtml(userName) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
                } else {&lt;br /&gt;
                    html +=&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;clbi-recent-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;clbi-recent-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;a href=&amp;quot;&#039; + escapeHtml(pageHref) + &#039;&amp;quot; class=&amp;quot;clbi-recent-title&amp;quot;&amp;gt;&#039; + escapeHtml(title) + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;clbi-recent-time&amp;quot;&amp;gt;&#039; + escapeHtml(label) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList) {&lt;br /&gt;
                $target.html(&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-recent-viewport&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-recent-stack&amp;quot;&amp;gt;&#039; + html + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
                );&lt;br /&gt;
&lt;br /&gt;
                if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
                    ensureNewsBottomFinish();&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                $target.html(html);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (isNewsList &amp;amp;&amp;amp; typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
                scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $target.find(isNewsList ? &#039;.news-recent-item&#039; : &#039;.clbi-recent-item&#039;).each(function() {&lt;br /&gt;
                var wrap = $(this).find(isNewsList ? &#039;.news-recent-title-wrap&#039; : &#039;.clbi-recent-title-wrap&#039;);&lt;br /&gt;
                var title = $(this).find(isNewsList ? &#039;.news-recent-title&#039; : &#039;.clbi-recent-title&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (!wrap.length || !title.length) return;&lt;br /&gt;
&lt;br /&gt;
                var wrapW = wrap.width();&lt;br /&gt;
                var titleW = title[0].scrollWidth;&lt;br /&gt;
&lt;br /&gt;
                if (titleW &amp;gt; wrapW + 20) {&lt;br /&gt;
                    var duration = titleW / 40;&lt;br /&gt;
&lt;br /&gt;
                    title.css({&lt;br /&gt;
                        animation: &#039;clbi-scroll &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
                        &#039;--scroll-dist&#039;: &#039;-&#039; + (titleW - wrapW + 8) + &#039;px&#039;&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    ).fail(function() {&lt;br /&gt;
        var lang = getCurrentLang();&lt;br /&gt;
        var t = (window.LANG &amp;amp;&amp;amp; window.LANG[lang]) ? window.LANG[lang] : (window.LANG ? window.LANG.ko : null);&lt;br /&gt;
&lt;br /&gt;
        $target.html((t &amp;amp;&amp;amp; t.loadFail) ? t.loadFail : &#039;불러오기 실패&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function ensureRecentViewport() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-left-recent-list&#039;);&lt;br /&gt;
    var viewport;&lt;br /&gt;
    var stack;&lt;br /&gt;
    var children;&lt;br /&gt;
&lt;br /&gt;
    if (!list) return null;&lt;br /&gt;
&lt;br /&gt;
    viewport = list.querySelector(&#039;:scope &amp;gt; .news-recent-viewport&#039;);&lt;br /&gt;
    stack = viewport ? viewport.querySelector(&#039;:scope &amp;gt; .news-recent-stack&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (viewport &amp;amp;&amp;amp; stack) return viewport;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(list.children || []);&lt;br /&gt;
&lt;br /&gt;
    viewport = document.createElement(&#039;div&#039;);&lt;br /&gt;
    viewport.className = &#039;news-recent-viewport&#039;;&lt;br /&gt;
&lt;br /&gt;
    stack = document.createElement(&#039;div&#039;);&lt;br /&gt;
    stack.className = &#039;news-recent-stack&#039;;&lt;br /&gt;
&lt;br /&gt;
    children.forEach(function (child) {&lt;br /&gt;
        if (child.classList &amp;amp;&amp;amp; child.classList.contains(&#039;news-recent-viewport&#039;)) return;&lt;br /&gt;
        stack.appendChild(child);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    viewport.appendChild(stack);&lt;br /&gt;
    list.appendChild(viewport);&lt;br /&gt;
&lt;br /&gt;
    return viewport;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function ensureNewsBottomFinish() {&lt;br /&gt;
    var newsBox = document.querySelector(&#039;#clbi-left-sidebar .clbi-left-news-box&#039;);&lt;br /&gt;
    var content = newsBox ? newsBox.querySelector(&#039;.clbi-news-box&#039;) : null;&lt;br /&gt;
    var finish;&lt;br /&gt;
&lt;br /&gt;
    if (!content) return null;&lt;br /&gt;
&lt;br /&gt;
    finish = content.querySelector(&#039;:scope &amp;gt; .news-bottom-finish&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!finish) {&lt;br /&gt;
        finish = document.createElement(&#039;div&#039;);&lt;br /&gt;
        finish.className = &#039;news-bottom-finish&#039;;&lt;br /&gt;
        finish.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        content.appendChild(finish);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return finish;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateAdaptiveLeftRecentItems() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: 좌측 사이드는 뉴스 확장형 flex 레이아웃이 높이를 담당한다.&lt;br /&gt;
    이전 adaptive 코드는 항목을 숨기거나 mask/fade를 걸기 위한 것이었으므로&lt;br /&gt;
    여기서는 잔여 상태만 정리하고 DOM 래퍼만 보장한다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftRecentAdaptiveState();&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureRecentViewport === &#039;function&#039;) {&lt;br /&gt;
        ensureRecentViewport();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof ensureNewsBottomFinish === &#039;function&#039;) {&lt;br /&gt;
        ensureNewsBottomFinish();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleClbiContentBottomGap === &#039;function&#039;) {&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleAdaptiveLeftRecentItems() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateAdaptiveLeftRecentItems();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 80);&lt;br /&gt;
    window.setTimeout(updateAdaptiveLeftRecentItems, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateClbiContentBottomGap(iteration) {&lt;br /&gt;
    var content = document.querySelector(&#039;.container-fluid.liberty-content&#039;);&lt;br /&gt;
    var main = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
    var bottomNav = document.getElementById(&#039;clbi-bottom-nav-wrap&#039;);&lt;br /&gt;
    var desiredGap = 8;&lt;br /&gt;
    var rootStyle;&lt;br /&gt;
    var scale = 1;&lt;br /&gt;
    var contentRect;&lt;br /&gt;
    var bottomTop;&lt;br /&gt;
    var targetHeight;&lt;br /&gt;
    var currentHeight;&lt;br /&gt;
    var visualGap;&lt;br /&gt;
&lt;br /&gt;
    iteration = iteration || 0;&lt;br /&gt;
&lt;br /&gt;
    if (!content || !main || !bottomNav) return;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    하단 간격은 scale 모드에서도 같은 기준으로 계산한다.&lt;br /&gt;
    transform:scale()이 걸리면 getBoundingClientRect()는 축소된 화면 좌표를 반환하므로,&lt;br /&gt;
    목표 간격 8px도 scale을 곱한 화면 좌표로 비교하고 다시 design px로 환산한다.&lt;br /&gt;
&lt;br /&gt;
    목표:&lt;br /&gt;
    .liberty-content-main.bottom === #clbi-bottom-nav-wrap.top - 8px&lt;br /&gt;
    */&lt;br /&gt;
    if (document.body &amp;amp;&amp;amp; document.body.classList &amp;amp;&amp;amp; document.body.classList.contains(&#039;clbi-shell-vertical-scale&#039;)) {&lt;br /&gt;
        rootStyle = window.getComputedStyle(document.documentElement);&lt;br /&gt;
        scale = parseFloat(rootStyle.getPropertyValue(&#039;--clbi-shell-scale&#039;)) || 1;&lt;br /&gt;
        scale = Math.max(0.25, scale);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    contentRect = content.getBoundingClientRect();&lt;br /&gt;
    bottomTop = bottomNav.getBoundingClientRect().top;&lt;br /&gt;
    visualGap = desiredGap * scale;&lt;br /&gt;
    targetHeight = Math.floor((bottomTop - contentRect.top - visualGap) / scale);&lt;br /&gt;
    targetHeight = Math.max(120, targetHeight);&lt;br /&gt;
&lt;br /&gt;
    currentHeight = Math.round(content.getBoundingClientRect().height / scale);&lt;br /&gt;
&lt;br /&gt;
    content.style.setProperty(&#039;--clbi-content-extra&#039;, &#039;0px&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
    content.style.setProperty(&#039;max-height&#039;, targetHeight + &#039;px&#039;, &#039;important&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (Math.abs(currentHeight - targetHeight) &amp;gt;= 1 &amp;amp;&amp;amp; iteration &amp;lt; 4) {&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            updateClbiContentBottomGap(iteration + 1);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleClbiContentBottomGap() {&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    });&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 40);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 120);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 280);&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        updateClbiContentBottomGap(0);&lt;br /&gt;
    }, 520);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function updateLeftBillboardAdaptive() {&lt;br /&gt;
    /*&lt;br /&gt;
    134 기준: Ad는 이미지/CRT 비율을 유지하는 고정 슬롯이다.&lt;br /&gt;
    남는 세로 공간은 뉴스 박스가 흡수하므로, Ad에 하단 finish를 늘리거나&lt;br /&gt;
    title-only 상태로 접는 adaptive 보정은 사용하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    resetLeftBillboardAdaptiveState();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftBillboardAdaptive() {&lt;br /&gt;
    window.requestAnimationFrame(updateLeftBillboardAdaptive);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 80);&lt;br /&gt;
    window.setTimeout(updateLeftBillboardAdaptive, 240);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function scheduleLeftSidebarVerticalFit() {&lt;br /&gt;
    if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.setTimeout(function () {&lt;br /&gt;
        if (typeof scheduleAdaptiveLeftRecentItems === &#039;function&#039;) {&lt;br /&gt;
            scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof scheduleLeftBillboardAdaptive === &#039;function&#039;) {&lt;br /&gt;
            scheduleLeftBillboardAdaptive();&lt;br /&gt;
        }&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시대 문서 전용 왼쪽 사이드바 이미지&lt;br /&gt;
function updateLeftSidebarNationsImage() {&lt;br /&gt;
    $(&#039;#clbi-left-nations-image&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setProfileActionLabel(selector, text) {&lt;br /&gt;
    var target = $(selector);&lt;br /&gt;
    var label = target.find(&#039;.profile-action-label&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (label.length) {&lt;br /&gt;
        label.text(text);&lt;br /&gt;
    } else {&lt;br /&gt;
        target.text(text);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 사이드바 업데이트&lt;br /&gt;
function updateSidebar() {&lt;br /&gt;
    if (!window.LANG) {&lt;br /&gt;
        setTimeout(updateSidebar, 100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
&lt;br /&gt;
    var newsTitle = t.news || &#039;뉴스&#039;;&lt;br /&gt;
    var changelogTitle = t.changelog || &#039;체인지로그&#039;;&lt;br /&gt;
    var recentTitle = t.recentChanges || &#039;최근 변경&#039;;&lt;br /&gt;
    var languageTitle = t.language || &#039;언어&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-language&#039;).text(languageTitle);&lt;br /&gt;
    renderSidebarLanguageBox();&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-left-news&#039;).text(newsTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
    $(&#039;#clbi-left-news-recent-main&#039;).text(recentTitle);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#clbi-title-search a&#039;).text(t.search);&lt;br /&gt;
    $(&#039;#clbi-search-input&#039;).attr(&#039;placeholder&#039;, t.search + &#039;...&#039;);&lt;br /&gt;
    $(&#039;#clbi-title-recent a&#039;).text(recentTitle);&lt;br /&gt;
    $(&#039;#clbi-title-guide-label&#039;).text(t.guide);&lt;br /&gt;
    $(&#039;#clbi-guide-link&#039;).text(t.getStarted);&lt;br /&gt;
    $(&#039;#clbi-title-links-label&#039;).text(t.links);&lt;br /&gt;
&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-contribution&#039;, t.contribution);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-watchlist&#039;, t.watchlist);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-preferences&#039;, t.preferences);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-logout&#039;, t.logout);&lt;br /&gt;
    setProfileActionLabel(&#039;#clbi-btn-login&#039;, t.login);&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var specialPage = String(mw.config.get(&#039;wgCanonicalSpecialPageName&#039;) || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
$(&#039;#clbi-left-news-changelog-main&#039;).text(changelogTitle);&lt;br /&gt;
$(&#039;#clbi-left-news-recent-title&#039;).text(&#039;RECENT CHANGES&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.clbi-user-btn&#039;).removeClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;Contributions&#039; ||&lt;br /&gt;
        specialPage === &#039;기여&#039; ||&lt;br /&gt;
        pageName.indexOf(&#039;특수:기여&#039;) === 0 ||&lt;br /&gt;
        pageName.indexOf(&#039;Special:Contributions&#039;) === 0&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-contribution&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (specialPage === &#039;Watchlist&#039;) {&lt;br /&gt;
        $(&#039;#clbi-btn-watchlist&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (&lt;br /&gt;
        specialPage === &#039;설정&#039; ||&lt;br /&gt;
        pageName === &#039;특수:설정&#039; ||&lt;br /&gt;
        pageName === &#039;Special:설정&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        $(&#039;#clbi-btn-preferences&#039;).addClass(&#039;clbi-user-btn-active&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.toggleBtn&#039;).each(function() {&lt;br /&gt;
        var btn = $(this);&lt;br /&gt;
&lt;br /&gt;
        if (!$(&#039;#&#039; + btn.data(&#039;target&#039;)).hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            btn.text(t.expand);&lt;br /&gt;
        } else {&lt;br /&gt;
            btn.text(t.collapse);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    updateLeftSidebarNationsImage();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function canShowContentTools() {&lt;br /&gt;
    // 비로그인 사용자는 편집/역사/공유 버튼을 숨김&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // MediaWiki가 현재 문서를 편집 가능하지 않다고 판단하면 숨김&lt;br /&gt;
    var isEditable = mw.config.get(&#039;wgIsProbablyEditable&#039;);&lt;br /&gt;
    if (isEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var relevantEditable = mw.config.get(&#039;wgRelevantPageIsProbablyEditable&#039;);&lt;br /&gt;
    if (relevantEditable === false) {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkNodes(root) {&lt;br /&gt;
    var seen = [];&lt;br /&gt;
    var nodes = [];&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
&lt;br /&gt;
    $root.find(&#039;#catlinks, .catlinks&#039;).add($root.filter(&#039;#catlinks, .catlinks&#039;)).each(function () {&lt;br /&gt;
        if (seen.indexOf(this) !== -1) return;&lt;br /&gt;
        seen.push(this);&lt;br /&gt;
        nodes.push(this);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return nodes;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinksTarget(root) {&lt;br /&gt;
    var $root = root ? $(root) : $(document);&lt;br /&gt;
    var parserOutput = $root.find(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
    var main = $root.find(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.liberty-content-main&#039;)) {&lt;br /&gt;
        parserOutput = $(root).find(&#039;.mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!parserOutput.length &amp;amp;&amp;amp; root &amp;amp;&amp;amp; $(root).is(&#039;.mw-parser-output&#039;)) {&lt;br /&gt;
        parserOutput = $(root);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (parserOutput.length) return parserOutput;&lt;br /&gt;
    if (main.length) return main;&lt;br /&gt;
&lt;br /&gt;
    if (!root) {&lt;br /&gt;
        parserOutput = $(&#039;.liberty-content-main .mw-parser-output&#039;).first();&lt;br /&gt;
        main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
        if (parserOutput.length) return parserOutput;&lt;br /&gt;
        if (main.length) return main;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return $();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var CLBI_CATLINKS_FETCH_TOKEN = 0;&lt;br /&gt;
&lt;br /&gt;
function getCurrentPageTitleForCatlinks() {&lt;br /&gt;
    return String(&lt;br /&gt;
        mw.config.get(&#039;wgPageName&#039;) ||&lt;br /&gt;
        mw.config.get(&#039;wgRelevantPageName&#039;) ||&lt;br /&gt;
        &#039;&#039;&lt;br /&gt;
    ).trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function shouldFetchCatlinks() {&lt;br /&gt;
    var pageName = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!pageName) return false;&lt;br /&gt;
    if (specialPage) return false;&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function clearCatlinksInlineHiding(cat) {&lt;br /&gt;
    if (!cat || !cat.style) return;&lt;br /&gt;
&lt;br /&gt;
    cat.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
    cat.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-normal-catlinks, #mw-normal-catlinks, .mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        if (!this.style) return;&lt;br /&gt;
        this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
        this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function exposeHiddenCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    $(cat).find(&#039;.mw-hidden-catlinks, #mw-hidden-catlinks, .mw-hidden-cats-hidden, .mw-hidden-cats-user-shown&#039;).each(function () {&lt;br /&gt;
        this.classList.remove(&#039;mw-hidden-cats-hidden&#039;);&lt;br /&gt;
        this.classList.add(&#039;mw-hidden-cats-user-shown&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (this.style) {&lt;br /&gt;
            this.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;max-height&#039;);&lt;br /&gt;
            this.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getCatlinkTextContent(cat) {&lt;br /&gt;
    var clone;&lt;br /&gt;
    var text;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    clone = cat.cloneNode(true);&lt;br /&gt;
    $(clone).find(&#039;script, style&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    text = String(clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*:\s*/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^(분류|숨은 분류|Category|Hidden categories)\s*$/i, &#039;&#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
&lt;br /&gt;
    return text;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function hasRenderedCatlinkItems(cat) {&lt;br /&gt;
    var hasLink;&lt;br /&gt;
    var hasListText;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    hasLink = false;&lt;br /&gt;
    $(cat).find(&#039;a&#039;).each(function () {&lt;br /&gt;
        var text = String($(this).text() || &#039;&#039;).trim();&lt;br /&gt;
        var href = String(this.getAttribute(&#039;href&#039;) || &#039;&#039;).trim();&lt;br /&gt;
        if (text || href) hasLink = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasLink) return true;&lt;br /&gt;
&lt;br /&gt;
    hasListText = false;&lt;br /&gt;
    $(cat).find(&#039;li&#039;).each(function () {&lt;br /&gt;
        if (String($(this).text() || &#039;&#039;).trim()) hasListText = true;&lt;br /&gt;
    });&lt;br /&gt;
    if (hasListText) return true;&lt;br /&gt;
&lt;br /&gt;
    return !!getCatlinkTextContent(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCategoryTitle(rawTitle) {&lt;br /&gt;
    var title = String(rawTitle == null ? &#039;&#039; : rawTitle).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!title) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    title = title.replace(/_/g, &#039; &#039;);&lt;br /&gt;
&lt;br /&gt;
    if (/^(Category|분류):/i.test(title)) {&lt;br /&gt;
        return title;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;분류:&#039; + title;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function makeCategoryLinkTitle(rawTitle) {&lt;br /&gt;
    return String(rawTitle || &#039;&#039;)&lt;br /&gt;
        .replace(/^Category:/i, &#039;&#039;)&lt;br /&gt;
        .replace(/^분류:/, &#039;&#039;)&lt;br /&gt;
        .replace(/_/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function dedupeCatlinkCategories(categories) {&lt;br /&gt;
    var seen = {};&lt;br /&gt;
    var result = [];&lt;br /&gt;
&lt;br /&gt;
    (categories || []).forEach(function (item) {&lt;br /&gt;
        var title = &#039;&#039;;&lt;br /&gt;
        var hidden = false;&lt;br /&gt;
&lt;br /&gt;
        if (typeof item === &#039;string&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item);&lt;br /&gt;
        } else if (item &amp;amp;&amp;amp; typeof item === &#039;object&#039;) {&lt;br /&gt;
            title = normalizeCategoryTitle(item.title || item.name || item.category || &#039;&#039;);&lt;br /&gt;
            hidden = item.hidden !== undefined || item.isHidden === true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
        if (seen[title]) return;&lt;br /&gt;
&lt;br /&gt;
        seen[title] = true;&lt;br /&gt;
        result.push({ title: title, hidden: hidden });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return result;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getConfigCatlinksCategories() {&lt;br /&gt;
    var normal = mw.config.get(&#039;wgCategories&#039;) || [];&lt;br /&gt;
    var hidden = mw.config.get(&#039;wgHiddenCategories&#039;) || [];&lt;br /&gt;
    var categories = [];&lt;br /&gt;
&lt;br /&gt;
    if (!Array.isArray(normal)) normal = [];&lt;br /&gt;
    if (!Array.isArray(hidden)) hidden = [];&lt;br /&gt;
&lt;br /&gt;
    normal.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: false });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    hidden.forEach(function (name) {&lt;br /&gt;
        categories.push({ title: normalizeCategoryTitle(name), hidden: true });&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return dedupeCatlinkCategories(categories);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksReady(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksEmpty(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-fetching&#039;);&lt;br /&gt;
    cat.removeAttribute(&#039;data-clbi-catlinks-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markCatlinksPending(cat, pageTitle) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-ready&#039;);&lt;br /&gt;
    cat.classList.remove(&#039;clbi-catlinks-empty&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-pending&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-loading&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-fetching&#039;, &#039;1&#039;);&lt;br /&gt;
    cat.setAttribute(&#039;data-clbi-catlinks-page&#039;, pageTitle || getCurrentPageTitleForCatlinks());&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderFetchedCatlinks(cat, categories, pageTitle) {&lt;br /&gt;
    var container;&lt;br /&gt;
    var ul;&lt;br /&gt;
    var normalized;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return false;&lt;br /&gt;
&lt;br /&gt;
    normalized = dedupeCatlinkCategories(categories);&lt;br /&gt;
&lt;br /&gt;
    if (!normalized.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    cat.innerHTML = &#039;&#039;;&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    cat.classList.add(&#039;clbi-catlinks-api-populated&#039;);&lt;br /&gt;
&lt;br /&gt;
    container = document.createElement(&#039;div&#039;);&lt;br /&gt;
    container.className = &#039;mw-normal-catlinks&#039;;&lt;br /&gt;
    container.appendChild(document.createTextNode(&#039;분류: &#039;));&lt;br /&gt;
&lt;br /&gt;
    ul = document.createElement(&#039;ul&#039;);&lt;br /&gt;
&lt;br /&gt;
    normalized.forEach(function (item) {&lt;br /&gt;
        var title = String(item &amp;amp;&amp;amp; item.title ? item.title : &#039;&#039;).trim();&lt;br /&gt;
        var li;&lt;br /&gt;
        var a;&lt;br /&gt;
&lt;br /&gt;
        if (!title) return;&lt;br /&gt;
&lt;br /&gt;
        li = document.createElement(&#039;li&#039;);&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = mw.util.getUrl(title);&lt;br /&gt;
        a.title = title;&lt;br /&gt;
        a.textContent = makeCategoryLinkTitle(title);&lt;br /&gt;
&lt;br /&gt;
        if (item.hidden) {&lt;br /&gt;
            li.className = &#039;clbi-hidden-category-item&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        li.appendChild(a);&lt;br /&gt;
        ul.appendChild(li);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (!ul.children.length) {&lt;br /&gt;
        markCatlinksEmpty(cat);&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.appendChild(ul);&lt;br /&gt;
    cat.appendChild(container);&lt;br /&gt;
    markCatlinksReady(cat, pageTitle);&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksForPage(pageTitle, callback) {&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (typeof pageTitle === &#039;function&#039;) {&lt;br /&gt;
        callback = pageTitle;&lt;br /&gt;
        pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    pageTitle = String(pageTitle || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
    if (!pageTitle || !shouldFetchCatlinks()) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;categories&#039;,&lt;br /&gt;
        titles: pageTitle,&lt;br /&gt;
        cllimit: &#039;max&#039;,&lt;br /&gt;
        clprop: &#039;hidden&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).done(function (data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages &amp;amp;&amp;amp; pages.length ? pages[0] : null;&lt;br /&gt;
        var categories = page &amp;amp;&amp;amp; page.categories ? page.categories : [];&lt;br /&gt;
&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback(categories || [], pageTitle);&lt;br /&gt;
    }).fail(function () {&lt;br /&gt;
        if (typeof callback === &#039;function&#039;) callback([], pageTitle);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function finalizeEmptyCatlinks(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    markCatlinksEmpty(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchCatlinksIfNeeded(cat) {&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
    if (!cat.isConnected) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(cat, configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) {&lt;br /&gt;
        finalizeEmptyCatlinks(cat);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (cat.getAttribute(&#039;data-clbi-catlinks-fetching&#039;) === &#039;1&#039; &amp;amp;&amp;amp; cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) === pageTitle) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
    markCatlinksPending(cat, pageTitle);&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (!cat || !cat.isConnected) return;&lt;br /&gt;
        if (cat.getAttribute(&#039;data-clbi-catlinks-page&#039;) !== requestedPage) return;&lt;br /&gt;
&lt;br /&gt;
        if (!renderFetchedCatlinks(cat, categories, requestedPage)) {&lt;br /&gt;
            finalizeEmptyCatlinks(cat);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function normalizeCatlinksPanel(cat) {&lt;br /&gt;
    if (!cat) return;&lt;br /&gt;
&lt;br /&gt;
    cat.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
    clearCatlinksInlineHiding(cat);&lt;br /&gt;
    exposeHiddenCatlinks(cat);&lt;br /&gt;
&lt;br /&gt;
    if (hasRenderedCatlinkItems(cat)) {&lt;br /&gt;
        markCatlinksReady(cat, getCurrentPageTitleForCatlinks());&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksIfNeeded(cat);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function createCatlinksPanel(target, className) {&lt;br /&gt;
    var cat;&lt;br /&gt;
&lt;br /&gt;
    if (!target || !target.length) return null;&lt;br /&gt;
&lt;br /&gt;
    cat = document.createElement(&#039;div&#039;);&lt;br /&gt;
    cat.id = &#039;catlinks&#039;;&lt;br /&gt;
    cat.className = className || &#039;catlinks clbi-catlinks-created clbi-catlinks-pending&#039;;&lt;br /&gt;
    target.append(cat);&lt;br /&gt;
    return cat;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prepareSpaCatlinksBeforeInsert(root) {&lt;br /&gt;
    var nodes;&lt;br /&gt;
    var target;&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    nodes = getCatlinkNodes(root);&lt;br /&gt;
&lt;br /&gt;
    nodes.forEach(function (node) {&lt;br /&gt;
        node.classList.add(&#039;catlinks&#039;);&lt;br /&gt;
        clearCatlinksInlineHiding(node);&lt;br /&gt;
        exposeHiddenCatlinks(node);&lt;br /&gt;
&lt;br /&gt;
        if (hasRenderedCatlinkItems(node)) {&lt;br /&gt;
            markCatlinksReady(node, pageTitle);&lt;br /&gt;
        } else if (configCategories.length) {&lt;br /&gt;
            renderFetchedCatlinks(node, configCategories, pageTitle);&lt;br /&gt;
        } else {&lt;br /&gt;
            markCatlinksEmpty(node);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (nodes.length || !configCategories.length) return;&lt;br /&gt;
&lt;br /&gt;
    target = getCatlinksTarget(root);&lt;br /&gt;
    if (!target.length) target = $(root);&lt;br /&gt;
&lt;br /&gt;
    renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function moveCatlinksToBottom() {&lt;br /&gt;
    var main = $(&#039;.liberty-content-main&#039;).first();&lt;br /&gt;
    var target = getCatlinksTarget();&lt;br /&gt;
    var catlinks = getCatlinkNodes();&lt;br /&gt;
    var configCategories;&lt;br /&gt;
    var pageTitle;&lt;br /&gt;
    var requestToken;&lt;br /&gt;
&lt;br /&gt;
    if (!main.length || !target.length) return;&lt;br /&gt;
&lt;br /&gt;
    pageTitle = getCurrentPageTitleForCatlinks();&lt;br /&gt;
&lt;br /&gt;
    catlinks.forEach(function (node) {&lt;br /&gt;
        var catNode = $(node);&lt;br /&gt;
&lt;br /&gt;
        if (node.parentNode !== target[0]) {&lt;br /&gt;
            catNode.appendTo(target);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        normalizeCatlinksPanel(node);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (catlinks.length) return;&lt;br /&gt;
&lt;br /&gt;
    configCategories = getConfigCatlinksCategories();&lt;br /&gt;
    if (configCategories.length) {&lt;br /&gt;
        renderFetchedCatlinks(createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;), configCategories, pageTitle);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!shouldFetchCatlinks()) return;&lt;br /&gt;
&lt;br /&gt;
    requestToken = ++CLBI_CATLINKS_FETCH_TOKEN;&lt;br /&gt;
&lt;br /&gt;
    fetchCatlinksForPage(pageTitle, function (categories, requestedPage) {&lt;br /&gt;
        var cat;&lt;br /&gt;
&lt;br /&gt;
        if (requestToken !== CLBI_CATLINKS_FETCH_TOKEN) return;&lt;br /&gt;
        if (requestedPage !== getCurrentPageTitleForCatlinks()) return;&lt;br /&gt;
        if (getCatlinkNodes().length) return;&lt;br /&gt;
        if (!categories || !categories.length) return;&lt;br /&gt;
&lt;br /&gt;
        cat = createCatlinksPanel(target, &#039;catlinks catlinks-allhidden clbi-catlinks-created clbi-catlinks-pending&#039;);&lt;br /&gt;
        renderFetchedCatlinks(cat, categories, requestedPage);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 대문 스타일&lt;br /&gt;
function initCategoryNavIfAvailable(root) {&lt;br /&gt;
    /*&lt;br /&gt;
    CategoryNav.js는 대문 카테고리 네비를 SVG로 생성한다.&lt;br /&gt;
&lt;br /&gt;
    Common.js가 SPA로 본문을 갈아끼운 뒤에는 MediaWiki 원래 페이지 로드와 달리&lt;br /&gt;
    CategoryNav.js의 초기 DOMContentLoaded만으로는 새 mount를 다시 잡지 못할 수 있다.&lt;br /&gt;
    CategoryNav.js 자체도 mw.hook(&#039;wikipage.content&#039;)를 듣지만, 로드 순서와 SPA 타이밍이&lt;br /&gt;
    엇갈릴 수 있으므로 Common.js 쪽에서도 존재 여부를 확인한 뒤 한 번 더 호출한다.&lt;br /&gt;
&lt;br /&gt;
    이 함수는 CategoryNav.js가 아직 로드되지 않았으면 아무 것도 하지 않는다.&lt;br /&gt;
    */&lt;br /&gt;
    if (&lt;br /&gt;
        window.CLBI &amp;amp;&amp;amp;&lt;br /&gt;
        window.CLBI.categoryNav &amp;amp;&amp;amp;&lt;br /&gt;
        typeof window.CLBI.categoryNav.init === &#039;function&#039;&lt;br /&gt;
    ) {&lt;br /&gt;
        window.CLBI.categoryNav.init(root || document);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeLegacyMainPageHero() {&lt;br /&gt;
    /*&lt;br /&gt;
    기존 대문 전용 레거시 요소 정리&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    이전 대문 구조에서는 Common.js가 본문 바깥에 #clbi-main-logo를 직접 삽입하고,&lt;br /&gt;
    본문 안의 #clbi-main-crt-hero를 #clbi-main-crt-hero-wrap으로 감싸서&lt;br /&gt;
    .liberty-content-main 위쪽으로 재배치했다.&lt;br /&gt;
&lt;br /&gt;
    새 대문은 본문 내부의 .main-portal이 로고, 알림, 카테고리 네비, 이미지 피드,&lt;br /&gt;
    방명록, 상태 패널을 모두 담당한다. 따라서 Common.js가 별도 로고나 CRT 래퍼를&lt;br /&gt;
    삽입하면 새 로고/콘텐츠와 중복된다.&lt;br /&gt;
&lt;br /&gt;
    여기서는 JS가 만들던 바깥 로고와 CRT 래퍼를 제거하고, 예전 대문 원본이나&lt;br /&gt;
    캐시된 렌더 결과에 남아 있을 수 있는 #clbi-main-crt-hero도 제거한다.&lt;br /&gt;
    */&lt;br /&gt;
    $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
    $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function setNativePageTitleHiddenHard(hidden) {&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.liberty-content-header&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title&#039;,&lt;br /&gt;
        &#039;.liberty-content-header .title h1&#039;,&lt;br /&gt;
        &#039;.liberty-content-header h1&#039;,&lt;br /&gt;
        &#039;#firstHeading&#039;,&lt;br /&gt;
        &#039;.firstHeading&#039;,&lt;br /&gt;
        &#039;.mw-first-heading&#039;,&lt;br /&gt;
        &#039;.page-heading&#039;,&lt;br /&gt;
        &#039;.page-header&#039;,&lt;br /&gt;
        &#039;.mw-page-title-main&#039;,&lt;br /&gt;
        &#039;.mw-page-title-namespace&#039;,&lt;br /&gt;
        &#039;.mw-page-title-separator&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    document.querySelectorAll(selectors.join(&#039;,&#039;)).forEach(function(node) {&lt;br /&gt;
        if (!node || !node.style) return;&lt;br /&gt;
&lt;br /&gt;
        if (hidden) {&lt;br /&gt;
            node.setAttribute(&#039;data-clbi-title-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;display&#039;, &#039;none&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;visibility&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;min-height&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;margin&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;padding&#039;, &#039;0&#039;, &#039;important&#039;);&lt;br /&gt;
            node.style.setProperty(&#039;overflow&#039;, &#039;hidden&#039;, &#039;important&#039;);&lt;br /&gt;
        } else if (node.getAttribute(&#039;data-clbi-title-hidden&#039;) === &#039;true&#039;) {&lt;br /&gt;
            node.removeAttribute(&#039;data-clbi-title-hidden&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;display&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;visibility&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;min-height&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;margin&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;padding&#039;);&lt;br /&gt;
            node.style.removeProperty(&#039;overflow&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyDefaultPageTitleVisibility() {&lt;br /&gt;
    var hideTitle = true;&lt;br /&gt;
    var isSystemAssetPage = false;&lt;br /&gt;
&lt;br /&gt;
    if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isSystemAssetPage === &#039;function&#039;) {&lt;br /&gt;
        isSystemAssetPage = window.CLBI_PAGE_SHELL.isSystemAssetPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage) {&lt;br /&gt;
        hideTitle = true;&lt;br /&gt;
    } else if (window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.isBackendOrSystemPage === &#039;function&#039;) {&lt;br /&gt;
        hideTitle = !window.CLBI_PAGE_SHELL.isBackendOrSystemPage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;)&lt;br /&gt;
        .toggleClass(&#039;page-title-hidden&#039;, hideTitle)&lt;br /&gt;
        .toggleClass(&#039;page-title-visible&#039;, !hideTitle)&lt;br /&gt;
        .toggleClass(&#039;clbi-system-doc-page&#039;, isSystemAssetPage);&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.renderSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.renderSystemDocIndicator();&lt;br /&gt;
    } else if (!isSystemAssetPage &amp;amp;&amp;amp; window.CLBI_PAGE_SHELL &amp;amp;&amp;amp; typeof window.CLBI_PAGE_SHELL.removeSystemDocIndicator === &#039;function&#039;) {&lt;br /&gt;
        window.CLBI_PAGE_SHELL.removeSystemDocIndicator();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (hideTitle) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;#firstHeading, .firstHeading, .mw-first-heading, .page-heading, .page-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(false);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function applyMainPageStyle() {&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    if (specialPage === &#039;Preferences&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var pageName = normalizePageName(mw.config.get(&#039;wgPageName&#039;));&lt;br /&gt;
    var namespaceNumber = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var isMainPage = (pageName === &#039;대문&#039;);&lt;br /&gt;
    var isUserProfilePage = (namespaceNumber === 2);&lt;br /&gt;
    var isScreenDoc = ($(&#039;.screen-header&#039;).length &amp;gt; 0);&lt;br /&gt;
    var hideTools = (isMainPage || isUserProfilePage || !canShowContentTools());&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, isUserProfilePage);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;clbi-main-page&#039;, isMainPage);&lt;br /&gt;
&lt;br /&gt;
    // 모든 문서에서 분류 바를 본문 컨테이너 아래로 이동&lt;br /&gt;
    moveCatlinksToBottom();&lt;br /&gt;
&lt;br /&gt;
    if (isMainPage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        setNativePageTitleHiddenHard(true);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 새 대문은 .main-portal 본문 구조가 로고/히어로를 담당한다.&lt;br /&gt;
        // Common.js의 구식 바깥 로고/CRT 재배치 루틴은 사용하지 않는다.&lt;br /&gt;
        removeLegacyMainPageHero();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    } else if (isUserProfilePage) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else if (isScreenDoc) {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main&#039;).addClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
        if ($(&#039;#clbi-tools-box&#039;).length === 0 &amp;amp;&amp;amp; canShowContentTools()) {&lt;br /&gt;
            var $toolsBox = $(&#039;&amp;lt;div id=&amp;quot;clbi-tools-box&amp;quot; class=&amp;quot;clbi-left-box&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsTitle = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;관리&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            var $toolsContent = $(&#039;&amp;lt;div class=&amp;quot;clbi-left-content&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
            $toolsContent.append($(&#039;.content-tools .btn-group&#039;).clone(true));&lt;br /&gt;
            $toolsBox.append($toolsTitle).append($toolsContent);&lt;br /&gt;
            $(&#039;#clbi-left-sidebar&#039;).append($toolsBox);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    } else {&lt;br /&gt;
        $(&#039;.liberty-content-header&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.mw-page-title-main, .mw-page-title-namespace, .mw-page-title-separator&#039;).removeClass(&#039;clbi-hide&#039;);&lt;br /&gt;
        $(&#039;.catlinks&#039;).css(&#039;display&#039;, &#039;&#039;);&lt;br /&gt;
        $(&#039;.liberty-content-main&#039;).css(&#039;border-radius&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
        $(&#039;#clbi-main-logo&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-main-crt-hero-wrap&#039;).remove();&lt;br /&gt;
        $(&#039;#clbi-tools-box&#039;).remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!isUserProfilePage) {&lt;br /&gt;
        $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
        $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.content-tools&#039;).css(&#039;display&#039;, &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
    applyDefaultPageTitleVisibility();&lt;br /&gt;
    updateSidebar();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 본문 기본 목차 제거&lt;br /&gt;
function removeNativeTocFromContent() {&lt;br /&gt;
    $(&#039;.liberty-content-main #toc, .liberty-content-main .toc&#039;).remove();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 ID 가져오기&lt;br /&gt;
function getHeadingId(heading) {&lt;br /&gt;
    if (heading.id) {&lt;br /&gt;
        return heading.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline[id]&#039;);&lt;br /&gt;
    if (headline &amp;amp;&amp;amp; headline.id) {&lt;br /&gt;
        return headline.id;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: MediaWiki 문단 제목 텍스트 가져오기&lt;br /&gt;
function getHeadingText(heading) {&lt;br /&gt;
    var headline = heading.querySelector(&#039;.mw-headline&#039;);&lt;br /&gt;
    var source = headline || heading;&lt;br /&gt;
    var clone = source.cloneNode(true);&lt;br /&gt;
&lt;br /&gt;
    $(clone).find(&#039;.mw-editsection, .mw-editsection-bracket, .mw-editsection-divider&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    return (clone.textContent || &#039;&#039;)&lt;br /&gt;
        .replace(/\s+/g, &#039; &#039;)&lt;br /&gt;
        .trim();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 왼쪽 목차: 긴 제목에 자동 스크롤 적용&lt;br /&gt;
function initTocTitleScroll(root) {&lt;br /&gt;
    var $items = root&lt;br /&gt;
        ? $(root).find(&#039;.toc-scroll-text&#039;)&lt;br /&gt;
        : $(&#039;#side-toc-box .toc-scroll-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    $items.each(function () {&lt;br /&gt;
        var $text = $(this);&lt;br /&gt;
        var $wrap = $text.closest(&#039;.toc-scroll-wrap&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$wrap.length) return;&lt;br /&gt;
&lt;br /&gt;
        var wrapW = Math.floor($wrap.width());&lt;br /&gt;
        var textW = Math.ceil(this.scrollWidth);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 레이아웃 계산이 끝나지 않았으면 이번 실행에서는 건드리지 않는다.&lt;br /&gt;
        if (!wrapW || !textW) return;&lt;br /&gt;
&lt;br /&gt;
        if (textW &amp;lt;= wrapW + 12) {&lt;br /&gt;
            // 왼쪽 목차: 칸을 넘지 않는 제목은 전체 텍스트를 그대로 보여준다.&lt;br /&gt;
            $wrap.removeClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
            if ($text.data(&#039;toc-scroll-enabled&#039;)) {&lt;br /&gt;
                $text.css({&lt;br /&gt;
                    animation: &#039;&#039;,&lt;br /&gt;
                    &#039;animation-delay&#039;: &#039;&#039;,&lt;br /&gt;
                    &#039;--scroll-dist&#039;: &#039;&#039;&lt;br /&gt;
                });&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-enabled&#039;);&lt;br /&gt;
                $text.removeData(&#039;toc-scroll-key&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var scrollDist = &#039;-&#039; + (textW - wrapW + 10) + &#039;px&#039;;&lt;br /&gt;
        var duration = Math.max(7, textW / 38) * 1.25;&lt;br /&gt;
        var scrollKey = scrollDist + &#039;|&#039; + duration;&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목에는 오른쪽 페이드와 스크롤을 적용한다.&lt;br /&gt;
        $wrap.addClass(&#039;is-scrolling&#039;);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 같은 값으로 이미 적용된 애니메이션은 다시 초기화하지 않는다.&lt;br /&gt;
        if ($text.data(&#039;toc-scroll-key&#039;) === scrollKey) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $text.data(&#039;toc-scroll-enabled&#039;, true);&lt;br /&gt;
        $text.data(&#039;toc-scroll-key&#039;, scrollKey);&lt;br /&gt;
&lt;br /&gt;
        $text.css({&lt;br /&gt;
            // 왼쪽 목차: 페이지 진입 직후에는 잠시 읽을 시간을 준 뒤 흐르게 한다.&lt;br /&gt;
            animation: &#039;toc-scroll-blink-reset &#039; + duration + &#039;s linear infinite&#039;,&lt;br /&gt;
            &#039;animation-delay&#039;: &#039;1s&#039;,&lt;br /&gt;
            &#039;--scroll-dist&#039;: scrollDist&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 목차를 왼쪽 사이드바에 새로 생성&lt;br /&gt;
function moveTocToLeftSidebar() {&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: MediaWiki가 만든 원래 목차는 본문에서 제거한다.&lt;br /&gt;
    removeNativeTocFromContent();&lt;br /&gt;
&lt;br /&gt;
    var leftSidebar = document.getElementById(&#039;clbi-left-sidebar&#039;);&lt;br /&gt;
    if (!leftSidebar) return;&lt;br /&gt;
&lt;br /&gt;
    var content =&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main .mw-parser-output&#039;) ||&lt;br /&gt;
        document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!content) return;&lt;br /&gt;
&lt;br /&gt;
    var headings = Array.prototype.slice.call(&lt;br /&gt;
        content.querySelectorAll(&#039;h2, h3&#039;)&lt;br /&gt;
    ).filter(function (heading) {&lt;br /&gt;
        if (heading.closest(&#039;#toc, .toc, #side-toc-box&#039;)) return false;&lt;br /&gt;
&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
&lt;br /&gt;
        if (!id || !text) return false;&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var tocKey = headings.map(function (heading) {&lt;br /&gt;
        return getHeadingId(heading) + &#039;|&#039; + getHeadingText(heading);&lt;br /&gt;
    }).join(&#039;||&#039;);&lt;br /&gt;
&lt;br /&gt;
    var existingBox = document.getElementById(&#039;side-toc-box&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 같은 문서에서 같은 목차를 이미 만들었다면 다시 지우고 만들지 않는다.&lt;br /&gt;
    if (existingBox &amp;amp;&amp;amp; existingBox.getAttribute(&#039;data-toc-key&#039;) === tocKey) {&lt;br /&gt;
        initTocTitleScroll(existingBox);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (existingBox) {&lt;br /&gt;
        existingBox.remove();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!headings.length) return;&lt;br /&gt;
&lt;br /&gt;
    var tocBox = document.createElement(&#039;div&#039;);&lt;br /&gt;
    tocBox.className = &#039;clbi-left-box&#039;;&lt;br /&gt;
    tocBox.id = &#039;side-toc-box&#039;;&lt;br /&gt;
    tocBox.setAttribute(&#039;data-toc-key&#039;, tocKey);&lt;br /&gt;
&lt;br /&gt;
    var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
    title.className = &#039;clbi-left-title&#039;;&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: 박스 제목은 Lang.js의 현재 UI 언어를 따른다.&lt;br /&gt;
    var currentLang = getCurrentLang();&lt;br /&gt;
    var t = (window.LANG &amp;amp;&amp;amp; window.LANG[currentLang]) ? window.LANG[currentLang] : window.LANG.ko;&lt;br /&gt;
    var tocTitleText = (t &amp;amp;&amp;amp; t.toc) ? t.toc : &#039;목차&#039;;&lt;br /&gt;
&lt;br /&gt;
    title.textContent = tocTitleText;&lt;br /&gt;
&lt;br /&gt;
    var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
    body.className = &#039;clbi-left-content toc-sidebar-content&#039;;&lt;br /&gt;
&lt;br /&gt;
    var list = document.createElement(&#039;ul&#039;);&lt;br /&gt;
    list.className = &#039;generated-toc&#039;;&lt;br /&gt;
&lt;br /&gt;
    headings.forEach(function (heading) {&lt;br /&gt;
        var id = getHeadingId(heading);&lt;br /&gt;
        var text = getHeadingText(heading);&lt;br /&gt;
        var level = heading.tagName.toLowerCase() === &#039;h3&#039; ? 3 : 2;&lt;br /&gt;
&lt;br /&gt;
        var item = document.createElement(&#039;li&#039;);&lt;br /&gt;
        item.className = &#039;toc-level-&#039; + level;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.setAttribute(&#039;href&#039;, &#039;#&#039; + id);&lt;br /&gt;
&lt;br /&gt;
        // 왼쪽 목차: 긴 제목 스크롤을 위해 텍스트를 별도 span으로 감싼다.&lt;br /&gt;
        var textWrap = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textWrap.className = &#039;toc-scroll-wrap&#039;;&lt;br /&gt;
&lt;br /&gt;
        var textSpan = document.createElement(&#039;span&#039;);&lt;br /&gt;
        textSpan.className = &#039;toc-scroll-text&#039;;&lt;br /&gt;
        textSpan.textContent = text;&lt;br /&gt;
&lt;br /&gt;
        textWrap.appendChild(textSpan);&lt;br /&gt;
        link.appendChild(textWrap);&lt;br /&gt;
&lt;br /&gt;
        item.appendChild(link);&lt;br /&gt;
        list.appendChild(item);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    body.appendChild(list);&lt;br /&gt;
    tocBox.appendChild(title);&lt;br /&gt;
    tocBox.appendChild(body);&lt;br /&gt;
    leftSidebar.appendChild(tocBox);&lt;br /&gt;
&lt;br /&gt;
    // 왼쪽 목차: DOM 배치가 끝난 뒤 긴 제목 스크롤 여부를 계산한다.&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        initTocTitleScroll(tocBox);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            initTocTitleScroll(tocBox);&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 우측 광고판: 이미지/번역 캡션 목록&lt;br /&gt;
var RIGHT_BILLBOARD_ITEMS = [&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
        alt: &#039;PROOF TO THE WORLD / YOU ONCE PART OF IT&#039;,&lt;br /&gt;
        duration: 3000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;당신이 한때 이 세계의&#039;,&lt;br /&gt;
            &#039;&#039;,&lt;br /&gt;
            &#039;일부였다는 것을 증명하십시오&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-003.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    },&lt;br /&gt;
    {&lt;br /&gt;
        file: &#039;Side-visual-002.png&#039;,&lt;br /&gt;
        alt: &#039;APPLY NOW&#039;,&lt;br /&gt;
        duration: 1000,&lt;br /&gt;
        caption: [&lt;br /&gt;
            &#039;&amp;quot;지금 지원하세요!&amp;quot;&#039;&lt;br /&gt;
        ]&lt;br /&gt;
    }&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItem(index) {&lt;br /&gt;
    var items = RIGHT_BILLBOARD_ITEMS;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        return {&lt;br /&gt;
            file: &#039;Side-visual-001.png&#039;,&lt;br /&gt;
            alt: &#039;&#039;,&lt;br /&gt;
            caption: []&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var normalized = index % items.length;&lt;br /&gt;
    if (normalized &amp;lt; 0) normalized += items.length;&lt;br /&gt;
&lt;br /&gt;
    return items[normalized];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardImageUrl(fileName) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/&#039; + encodeURIComponent(fileName || &#039;Side-visual-001.png&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardCaptionHtml(item) {&lt;br /&gt;
    var lines = item &amp;amp;&amp;amp; item.caption ? item.caption : [];&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    function escapeCaptionText(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/\&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    lines.forEach(function(line) {&lt;br /&gt;
        var text = String(line == null ? &#039;&#039; : line);&lt;br /&gt;
        var isGap = !text.trim();&lt;br /&gt;
        var className = &#039;right-billboard-caption-line&#039; + (isGap ? &#039; is-gap&#039; : &#039;&#039;);&lt;br /&gt;
        html += &#039;&amp;lt;span class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; + (isGap ? &#039;&amp;amp;nbsp;&#039; : escapeCaptionText(text)) + &#039;&amp;lt;/span&amp;gt;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return html;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setRightBillboardItem(index) {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box) return;&lt;br /&gt;
&lt;br /&gt;
    var item = getRightBillboardItem(index);&lt;br /&gt;
    var src = getRightBillboardImageUrl(item.file);&lt;br /&gt;
    var images = box.querySelectorAll(&#039;.right-billboard-image&#039;);&lt;br /&gt;
    var caption = box.querySelector(&#039;#right-billboard-caption&#039;);&lt;br /&gt;
    var emptySub = box.querySelector(&#039;.right-billboard-empty-sub&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, String(index));&lt;br /&gt;
    box.classList.remove(&#039;is-empty&#039;);&lt;br /&gt;
&lt;br /&gt;
    Array.prototype.forEach.call(images, function(img) {&lt;br /&gt;
        img.style.display = &#039;&#039;;&lt;br /&gt;
        img.setAttribute(&#039;src&#039;, src);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, img.classList.contains(&#039;right-billboard-image-base&#039;) ? (item.alt || &#039;&#039;) : &#039;&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (caption) {&lt;br /&gt;
        caption.innerHTML = getRightBillboardCaptionHtml(item);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (emptySub) {&lt;br /&gt;
        emptySub.textContent = item.file || &#039;Side-visual-001.png&#039;;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getRightBillboardItemDuration(item) {&lt;br /&gt;
    var duration = item &amp;amp;&amp;amp; item.duration ? parseInt(item.duration, 10) : 3000;&lt;br /&gt;
&lt;br /&gt;
    if (Number.isNaN(duration) || duration &amp;lt; 500) {&lt;br /&gt;
        duration = 3000;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return duration;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initRightBillboardCarousel() {&lt;br /&gt;
    var box = document.querySelector(&#039;.right-billboard-box&#039;);&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-billboard-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    box.setAttribute(&#039;data-billboard-index&#039;, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    setRightBillboardItem(0);&lt;br /&gt;
&lt;br /&gt;
    if (!RIGHT_BILLBOARD_ITEMS || RIGHT_BILLBOARD_ITEMS.length &amp;lt;= 1) return;&lt;br /&gt;
&lt;br /&gt;
    function scheduleNext() {&lt;br /&gt;
        var current = parseInt(box.getAttribute(&#039;data-billboard-index&#039;) || &#039;0&#039;, 10);&lt;br /&gt;
        if (Number.isNaN(current)) current = 0;&lt;br /&gt;
&lt;br /&gt;
        var currentItem = getRightBillboardItem(current);&lt;br /&gt;
        var delay = getRightBillboardItemDuration(currentItem);&lt;br /&gt;
&lt;br /&gt;
        window.setTimeout(function() {&lt;br /&gt;
            if (!document.body.contains(box)) return;&lt;br /&gt;
&lt;br /&gt;
            if (!document.hidden) {&lt;br /&gt;
                setRightBillboardItem(current + 1);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            scheduleNext();&lt;br /&gt;
        }, delay);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scheduleNext();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function escapeRightBillboardAttr(value) {&lt;br /&gt;
    return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
        .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
        .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
        .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
        .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
        .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildRightBillboardBox() {&lt;br /&gt;
    var billboardInitial = getRightBillboardItem(0);&lt;br /&gt;
    var billboardSrc = getRightBillboardImageUrl(billboardInitial.file);&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box right-billboard-box left-billboard-box left-ad-box&amp;quot; data-billboard-index=&amp;quot;0&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title right-billboard-title left-ad-title left-ad-title-iconless&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-ad&amp;quot; class=&amp;quot;left-ad-title-label&amp;quot;&amp;gt;Looking for a job?&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content left-ad-content-shell&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;right-billboard-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-recess&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;right-billboard-screen&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;right-billboard-image&amp;quot; class=&amp;quot;right-billboard-image right-billboard-image-base&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&#039; + escapeRightBillboardAttr(billboardInitial.alt || &#039;&#039;) + &#039;&amp;quot; onload=&amp;quot;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.remove(\&#039;is-empty\&#039;);}&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;var b=this.closest(\&#039;.right-billboard-box\&#039;); if(b){b.classList.add(\&#039;is-empty\&#039;);}&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-image-bloom&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-a&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-b&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img class=&amp;quot;right-billboard-image right-billboard-slice right-billboard-slice-c&amp;quot; src=&amp;quot;&#039; + escapeRightBillboardAttr(billboardSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.style.display=\&#039;none\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-glitch&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-tear&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;right-billboard-empty&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-main&amp;quot;&amp;gt;SIGNAL EMPTY&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;right-billboard-empty-sub&amp;quot;&amp;gt;&#039; + escapeRightBillboardAttr(billboardInitial.file || &#039;Side-visual-001.png&#039;) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;right-billboard-caption&amp;quot; class=&amp;quot;right-billboard-caption&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + getRightBillboardCaptionHtml(billboardInitial) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;right-billboard-bottom-finish left-ad-bottom-finish&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
var GREAT_WALL_DATA_TITLE = &#039;프로젝트:The_Great_Wall/Data.json&#039;;&lt;br /&gt;
var GREAT_WALL_LIST_LIMIT = 0;&lt;br /&gt;
var greatWallState = {&lt;br /&gt;
    data: { entries: {} },&lt;br /&gt;
    loaded: false,&lt;br /&gt;
    loading: false,&lt;br /&gt;
    saving: false,&lt;br /&gt;
    selectedOwnEntry: false,&lt;br /&gt;
    statusText: &#039;&#039;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function normalizeGreatWallData(data) {&lt;br /&gt;
    var normalized = { entries: {} };&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    Object.keys(entries).forEach(function(key) {&lt;br /&gt;
        var item = entries[key];&lt;br /&gt;
        var user = item &amp;amp;&amp;amp; item.user ? String(item.user) : String(key || &#039;&#039;);&lt;br /&gt;
        var text = item &amp;amp;&amp;amp; item.text ? String(item.text) : &#039;&#039;;&lt;br /&gt;
        var timestamp = item &amp;amp;&amp;amp; item.timestamp ? String(item.timestamp) : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!user || !text) return;&lt;br /&gt;
&lt;br /&gt;
        normalized.entries[user] = {&lt;br /&gt;
            user: user,&lt;br /&gt;
            text: text.slice(0, 140),&lt;br /&gt;
            timestamp: timestamp || new Date(0).toISOString()&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    return normalized;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseGreatWallData(text) {&lt;br /&gt;
    var parsed;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        parsed = text ? JSON.parse(text) : {};&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;The Great Wall data parse failed:&#039;, err);&lt;br /&gt;
        parsed = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return normalizeGreatWallData(parsed);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function stringifyGreatWallData(data) {&lt;br /&gt;
    return JSON.stringify(normalizeGreatWallData(data), null, 2) + &#039;\n&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallRevisionText(page) {&lt;br /&gt;
    var rev;&lt;br /&gt;
    var slot;&lt;br /&gt;
&lt;br /&gt;
    if (!page || !page.revisions || !page.revisions.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    rev = page.revisions[0];&lt;br /&gt;
&lt;br /&gt;
    if (rev.slots &amp;amp;&amp;amp; rev.slots.main) {&lt;br /&gt;
        slot = rev.slots.main;&lt;br /&gt;
        return slot.content || slot[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return rev.content || rev[&#039;*&#039;] || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function fetchGreatWallData() {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
    return api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        prop: &#039;revisions&#039;,&lt;br /&gt;
        titles: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
        rvprop: &#039;content|timestamp&#039;,&lt;br /&gt;
        rvslots: &#039;main&#039;,&lt;br /&gt;
        formatversion: 2&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var pages = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.pages ? data.query.pages : [];&lt;br /&gt;
        var page = pages[0] || null;&lt;br /&gt;
&lt;br /&gt;
        if (!page || page.missing) {&lt;br /&gt;
            return { entries: {} };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parseGreatWallData(getGreatWallRevisionText(page));&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall load failed:&#039;, err);&lt;br /&gt;
        return { entries: {} };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallEntries(data) {&lt;br /&gt;
    var entries = data &amp;amp;&amp;amp; data.entries &amp;amp;&amp;amp; typeof data.entries === &#039;object&#039; ? data.entries : {};&lt;br /&gt;
&lt;br /&gt;
    return Object.keys(entries).map(function(key) {&lt;br /&gt;
        return entries[key];&lt;br /&gt;
    }).filter(function(item) {&lt;br /&gt;
        return item &amp;amp;&amp;amp; item.user &amp;amp;&amp;amp; item.text;&lt;br /&gt;
    }).sort(function(a, b) {&lt;br /&gt;
        return String(a.timestamp || &#039;&#039;).localeCompare(String(b.timestamp || &#039;&#039;));&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallMessageTime(timestamp) {&lt;br /&gt;
    var date = timestamp ? new Date(timestamp) : null;&lt;br /&gt;
    var month;&lt;br /&gt;
    var day;&lt;br /&gt;
    var hour;&lt;br /&gt;
    var minute;&lt;br /&gt;
&lt;br /&gt;
    if (!date || isNaN(date.getTime())) return &#039;—&#039;;&lt;br /&gt;
&lt;br /&gt;
    month = String(date.getMonth() + 1).padStart(2, &#039;0&#039;);&lt;br /&gt;
    day = String(date.getDate()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    hour = String(date.getHours()).padStart(2, &#039;0&#039;);&lt;br /&gt;
    minute = String(date.getMinutes()).padStart(2, &#039;0&#039;);&lt;br /&gt;
&lt;br /&gt;
    return month + &#039;.&#039; + day + &#039; &#039; + hour + &#039;:&#039; + minute;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getGreatWallAvatarSrc(user) {&lt;br /&gt;
    return &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(String(user || &#039;&#039;)) + &#039;.png&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallEntryHtml(item, currentUser, selectedOwnEntry) {&lt;br /&gt;
    var isOwn = currentUser &amp;amp;&amp;amp; item.user === currentUser;&lt;br /&gt;
    var tag = isOwn ? &#039;button&#039; : &#039;div&#039;;&lt;br /&gt;
    var attrs = isOwn ? &#039; type=&amp;quot;button&amp;quot; data-great-wall-own-entry=&amp;quot;1&amp;quot; aria-label=&amp;quot;Edit your wall message&amp;quot;&#039; : &#039;&#039;;&lt;br /&gt;
    var className = &#039;great-wall-entry&#039; + (isOwn ? &#039; is-own&#039; : &#039;&#039;) + (isOwn &amp;amp;&amp;amp; selectedOwnEntry ? &#039; is-selected&#039; : &#039;&#039;);&lt;br /&gt;
    var avatarSrc = getGreatWallAvatarSrc(item.user);&lt;br /&gt;
    var messageTime = getGreatWallMessageTime(item.timestamp);&lt;br /&gt;
    var isoTime = item.timestamp || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;&#039; + tag + attrs + &#039; class=&amp;quot;&#039; + className + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;img class=&amp;quot;great-wall-avatar&amp;quot; src=&amp;quot;&#039; + escapeClbiHtml(avatarSrc) + &#039;&amp;quot; alt=&amp;quot;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=&amp;amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;quot;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-bubble&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-entry-head&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-user&amp;quot;&amp;gt;@&#039; + escapeClbiHtml(item.user) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;great-wall-time&amp;quot; title=&amp;quot;&#039; + escapeClbiHtml(isoTime) + &#039;&amp;quot;&amp;gt;&#039; + escapeClbiHtml(messageTime) + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-text&amp;quot;&amp;gt;&#039; + escapeClbiHtml(item.text) + &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/&#039; + tag + &#039;&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var list = document.getElementById(&#039;great-wall-list&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
    var status = document.getElementById(&#039;great-wall-status&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var entries = getGreatWallEntries(greatWallState.data);&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var visibleEntries = [];&lt;br /&gt;
    var seen = {};&lt;br /&gt;
&lt;br /&gt;
    if (!box || !list) return;&lt;br /&gt;
&lt;br /&gt;
    box.classList.toggle(&#039;is-guest&#039;, !currentUser);&lt;br /&gt;
    box.classList.toggle(&#039;is-loading&#039;, !!greatWallState.loading);&lt;br /&gt;
    box.classList.toggle(&#039;is-saving&#039;, !!greatWallState.saving);&lt;br /&gt;
    box.classList.toggle(&#039;has-own-entry&#039;, !!ownEntry);&lt;br /&gt;
    box.classList.toggle(&#039;is-own-selected&#039;, !!greatWallState.selectedOwnEntry);&lt;br /&gt;
&lt;br /&gt;
    entries.forEach(function(item) {&lt;br /&gt;
        if (seen[item.user]) return;&lt;br /&gt;
        visibleEntries.push(item);&lt;br /&gt;
        seen[item.user] = true;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.loading &amp;amp;&amp;amp; !greatWallState.loaded) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else if (!visibleEntries.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;NO MARKS&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    } else {&lt;br /&gt;
        list.innerHTML = visibleEntries.map(function(item) {&lt;br /&gt;
            return buildGreatWallEntryHtml(item, currentUser, greatWallState.selectedOwnEntry);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (list &amp;amp;&amp;amp; list.scrollHeight &amp;gt; list.clientHeight) {&lt;br /&gt;
        list.scrollTop = list.scrollHeight;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (status) {&lt;br /&gt;
        // Status text is intentionally suppressed in the composer strip; the area is spacing-only UI.&lt;br /&gt;
        status.textContent = &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!input || !submit) return;&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.textContent = &#039;LOGIN&#039;;&lt;br /&gt;
        submit.disabled = false;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = &#039;Login required&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (greatWallState.saving || greatWallState.loading) {&lt;br /&gt;
        input.disabled = true;&lt;br /&gt;
        input.readOnly = false;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = greatWallState.saving ? &#039;SAVE&#039; : &#039;SYNC&#039;;&lt;br /&gt;
        if (deleteButton) {&lt;br /&gt;
            deleteButton.disabled = true;&lt;br /&gt;
            deleteButton.title = greatWallState.saving ? &#039;Saving&#039; : &#039;Syncing&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.disabled = !ownEntry;&lt;br /&gt;
        deleteButton.title = ownEntry ? &#039;Delete your mark&#039; : &#039;No mark to delete&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        input.disabled = false;&lt;br /&gt;
        input.readOnly = true;&lt;br /&gt;
        input.setAttribute(&#039;aria-readonly&#039;, &#039;true&#039;);&lt;br /&gt;
        input.value = &#039;&#039;;&lt;br /&gt;
        input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
        submit.disabled = true;&lt;br /&gt;
        submit.textContent = &#039;UPDATE&#039;;&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    input.disabled = false;&lt;br /&gt;
    input.readOnly = false;&lt;br /&gt;
    input.removeAttribute(&#039;aria-readonly&#039;);&lt;br /&gt;
    input.placeholder = &#039;담벼락&#039;;&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; greatWallState.selectedOwnEntry &amp;amp;&amp;amp; !input.value) {&lt;br /&gt;
        input.value = ownEntry.text || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    submit.disabled = false;&lt;br /&gt;
    submit.textContent = ownEntry ? &#039;UPDATE&#039; : &#039;POST&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function saveGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var text = input ? String(input.value || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (ownEntry &amp;amp;&amp;amp; !greatWallState.selectedOwnEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!text) {&lt;br /&gt;
        greatWallState.statusText = &#039;EMPTY MARK&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (text.length &amp;gt; 140) {&lt;br /&gt;
        text = text.slice(0, 140);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SAVING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        data.entries[currentUser] = {&lt;br /&gt;
            user: currentUser,&lt;br /&gt;
            text: text,&lt;br /&gt;
            timestamp: new Date().toISOString()&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Update The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK UPDATED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall save failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;SAVE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
function deleteGreatWallEntry() {&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
    var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
    var api;&lt;br /&gt;
&lt;br /&gt;
    if (!currentUser) {&lt;br /&gt;
        window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!ownEntry) {&lt;br /&gt;
        greatWallState.statusText = &#039;NO MARK TO DELETE&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.saving = true;&lt;br /&gt;
    greatWallState.statusText = &#039;DELETING&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        data = normalizeGreatWallData(data);&lt;br /&gt;
        if (data.entries &amp;amp;&amp;amp; data.entries[currentUser]) {&lt;br /&gt;
            delete data.entries[currentUser];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        api = new mw.Api();&lt;br /&gt;
        return api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
            action: &#039;edit&#039;,&lt;br /&gt;
            title: GREAT_WALL_DATA_TITLE,&lt;br /&gt;
            text: stringifyGreatWallData(data),&lt;br /&gt;
            summary: &#039;Delete The Great Wall entry&#039;,&lt;br /&gt;
            format: &#039;json&#039;&lt;br /&gt;
        }).then(function() {&lt;br /&gt;
            greatWallState.data = data;&lt;br /&gt;
            greatWallState.loaded = true;&lt;br /&gt;
            greatWallState.selectedOwnEntry = false;&lt;br /&gt;
            greatWallState.statusText = &#039;MARK DELETED&#039;;&lt;br /&gt;
            if (input) input.value = &#039;&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }).then(function() {&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    }, function(err) {&lt;br /&gt;
        console.error(&#039;The Great Wall delete failed:&#039;, err);&lt;br /&gt;
        greatWallState.saving = false;&lt;br /&gt;
        greatWallState.statusText = &#039;DELETE FAILED&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBoxWhenReady() {&lt;br /&gt;
    if (!document.getElementById(&#039;great-wall-sidebar&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    if (mw.Api) {&lt;br /&gt;
        initGreatWallBox();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw.loader &amp;amp;&amp;amp; mw.loader.using) {&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
            initGreatWallBox();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initGreatWallBox() {&lt;br /&gt;
    var box = document.getElementById(&#039;great-wall-sidebar&#039;);&lt;br /&gt;
    var input = document.getElementById(&#039;great-wall-input&#039;);&lt;br /&gt;
    var submit = document.getElementById(&#039;great-wall-submit&#039;);&lt;br /&gt;
    var deleteButton = document.getElementById(&#039;great-wall-delete&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!box || box.getAttribute(&#039;data-great-wall-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    if (!mw.Api) {&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    box.setAttribute(&#039;data-great-wall-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
    box.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var ownButton = e.target.closest ? e.target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;]&#039;) : null;&lt;br /&gt;
        var currentUser = mw.config.get(&#039;wgUserName&#039;) || &#039;&#039;;&lt;br /&gt;
        var ownEntry = currentUser &amp;amp;&amp;amp; greatWallState.data.entries ? greatWallState.data.entries[currentUser] : null;&lt;br /&gt;
&lt;br /&gt;
        if (!ownButton || !ownEntry) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = true;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
        if (input) {&lt;br /&gt;
            input.focus();&lt;br /&gt;
            input.setSelectionRange(input.value.length, input.value.length);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
        var target = e.target;&lt;br /&gt;
        var keepSelection;&lt;br /&gt;
&lt;br /&gt;
        if (!greatWallState.selectedOwnEntry || !target || !target.closest) return;&lt;br /&gt;
&lt;br /&gt;
        keepSelection = target.closest(&#039;[data-great-wall-own-entry=&amp;quot;1&amp;quot;], .great-wall-editor, .great-wall-compose-sector&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (keepSelection) return;&lt;br /&gt;
&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        if (input) input.value = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (submit) {&lt;br /&gt;
        submit.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            if (!mw.config.get(&#039;wgUserName&#039;)) {&lt;br /&gt;
                window.location.href = &#039;/index.php?title=특수:로그인&amp;amp;returnto=&#039; + encodeURIComponent(mw.config.get(&#039;wgPageName&#039;) || &#039;대문&#039;);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            saveGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (deleteButton) {&lt;br /&gt;
        deleteButton.addEventListener(&#039;click&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            deleteGreatWallEntry();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (input) {&lt;br /&gt;
        input.addEventListener(&#039;keydown&#039;, function(e) {&lt;br /&gt;
            if (e.key === &#039;Enter&#039;) {&lt;br /&gt;
                e.preventDefault();&lt;br /&gt;
                saveGreatWallEntry();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        input.addEventListener(&#039;input&#039;, function() {&lt;br /&gt;
            if (input.value.length &amp;gt; 140) {&lt;br /&gt;
                input.value = input.value.slice(0, 140);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    greatWallState.loading = true;&lt;br /&gt;
    greatWallState.statusText = &#039;SYNCING WALL&#039;;&lt;br /&gt;
    renderGreatWallBox();&lt;br /&gt;
&lt;br /&gt;
    fetchGreatWallData().then(function(data) {&lt;br /&gt;
        greatWallState.data = normalizeGreatWallData(data);&lt;br /&gt;
        greatWallState.loaded = true;&lt;br /&gt;
        greatWallState.loading = false;&lt;br /&gt;
        greatWallState.selectedOwnEntry = false;&lt;br /&gt;
        greatWallState.statusText = &#039;&#039;;&lt;br /&gt;
        renderGreatWallBox();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildGreatWallBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;great-wall-sidebar&amp;quot; class=&amp;quot;clbi-right-box great-wall-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title great-wall-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-great-wall&amp;quot;&amp;gt;The Great Wall&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content great-wall-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-list&amp;quot; class=&amp;quot;great-wall-list&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;great-wall-empty&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;great-wall-compose-sector&amp;quot; aria-label=&amp;quot;The Great Wall editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;great-wall-editor&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;input id=&amp;quot;great-wall-input&amp;quot; class=&amp;quot;great-wall-input&amp;quot; type=&amp;quot;text&amp;quot; maxlength=&amp;quot;140&amp;quot; autocomplete=&amp;quot;off&amp;quot; placeholder=&amp;quot;담벼락&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-submit&amp;quot; class=&amp;quot;great-wall-submit&amp;quot; type=&amp;quot;button&amp;quot;&amp;gt;POST&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button id=&amp;quot;great-wall-delete&amp;quot; class=&amp;quot;great-wall-delete&amp;quot; type=&amp;quot;button&amp;quot; disabled&amp;gt;DEL&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;great-wall-status&amp;quot; class=&amp;quot;great-wall-status&amp;quot;&amp;gt;SYNCING WALL&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function removeMainPortalGuestbookPreview() {&lt;br /&gt;
    var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
    var guestbook = portal ? portal.querySelector(&#039;.guestbook-device&#039;) : null;&lt;br /&gt;
    var sideScreen;&lt;br /&gt;
    var grid;&lt;br /&gt;
&lt;br /&gt;
    if (!portal || !guestbook) return;&lt;br /&gt;
&lt;br /&gt;
    sideScreen = guestbook.closest ? guestbook.closest(&#039;.side-screen&#039;) : null;&lt;br /&gt;
    grid = sideScreen &amp;amp;&amp;amp; sideScreen.closest ? sideScreen.closest(&#039;.console-grid&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
    if (guestbook.parentNode) {&lt;br /&gt;
        guestbook.parentNode.removeChild(guestbook);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (sideScreen &amp;amp;&amp;amp; !(sideScreen.textContent || &#039;&#039;).trim() &amp;amp;&amp;amp; !sideScreen.querySelector(&#039;img,svg,video,canvas,form,input,button,a&#039;)) {&lt;br /&gt;
        if (sideScreen.parentNode) {&lt;br /&gt;
            sideScreen.parentNode.removeChild(sideScreen);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (grid) {&lt;br /&gt;
            grid.style.gridTemplateColumns = &#039;minmax(0,1fr)&#039;;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    portal.classList.add(&#039;is-great-wall-relocated&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildSiteInformationBox() {&lt;br /&gt;
    return &#039;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-right-box site-info-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-title site-info-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span&amp;gt;정보&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-right-content site-info-content&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;policy-list&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/개인정보처리방침&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;개인정보처리방침&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/면책_조항&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;면책 조항&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/라이선스&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;라이선스&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;policy-row&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/크레딧&amp;quot; class=&amp;quot;site-info-policy-button&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-title&amp;quot;&amp;gt;크레딧&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;site-info-policy-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;social-strip&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://discord.gg/ctaeJ9d3Q5&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;DC&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://www.youtube.com/@nxdsxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;YT&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;https://x.com/nxd_sxn&amp;quot; target=&amp;quot;_blank&amp;quot; rel=&amp;quot;noopener noreferrer&amp;quot;&amp;gt;X&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;social-icon&amp;quot;&amp;gt;&amp;lt;a href=&amp;quot;/index.php/프로젝트:소개&amp;quot;&amp;gt;WIP:&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 초기화 함수&lt;br /&gt;
function initSidebars() {&lt;br /&gt;
    var header = $(&#039;.liberty-content-header&#039;);&lt;br /&gt;
    var content = $(&#039;.liberty-content&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (header.length &amp;amp;&amp;amp; content.length) {&lt;br /&gt;
        header.prependTo(content);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-right-sidebar&#039;).length === 0) {&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        var isLoggedIn = username !== null;&lt;br /&gt;
        var avatarSrc = isLoggedIn&lt;br /&gt;
            ? &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + username + &#039;.png&#039;&lt;br /&gt;
            : &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&#039;;&lt;br /&gt;
&lt;br /&gt;
        var userBox;&lt;br /&gt;
&lt;br /&gt;
        if (isLoggedIn) {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;/index.php?title=특수:Redirect/file/Pfp-default.png\&#039;;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;a href=&amp;quot;/index.php/사용자:&#039; + username + &#039;&amp;quot; id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;&#039; + username + &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-quick-actions&amp;quot; aria-label=&amp;quot;프로필 빠른 메뉴&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-inventory&amp;quot; aria-label=&amp;quot;인벤토리&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_PACKAGE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;인벤토리&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-achievements&amp;quot; aria-label=&amp;quot;업적&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_TROPHY + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;업적&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;profile-quick-btn&amp;quot; id=&amp;quot;profile-quick-notifications&amp;quot; aria-label=&amp;quot;알림&amp;quot;&amp;gt;&amp;lt;span id=&amp;quot;profile-quick-notification-icon&amp;quot; class=&amp;quot;profile-quick-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_BELL + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-quick-tip&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;알림&amp;lt;/span&amp;gt;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:기여/&#039; + username + &#039;&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-contribution&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_TEXT + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;기여&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:주시문서목록&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-watchlist&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SCAN_EYE + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;주시문서 목록&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php/특수:설정&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-preferences&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_SETTINGS + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;설정&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그아웃&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn clbi-user-btn-logout&amp;quot; id=&amp;quot;clbi-btn-logout&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그아웃&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            userBox =&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;clbi-right-box profile-card-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-user-avatar-wrap&amp;quot; class=&amp;quot;profile-identity-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-avatar-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;img id=&amp;quot;clbi-user-avatar&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-user-name-row&amp;quot; class=&amp;quot;profile-name-row profile-name-row-guest&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-user-name&amp;quot;&amp;gt;Guest&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;clbi-right-content profile-action-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;a href=&amp;quot;/index.php?title=특수:로그인&amp;amp;returnto=대문&amp;quot; class=&amp;quot;clbi-user-btn&amp;quot; id=&amp;quot;clbi-btn-login&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-action-icon&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; + CLBI_SVG_POWER + &#039;&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-action-label&amp;quot;&amp;gt;로그인&amp;lt;/span&amp;gt;&amp;lt;i class=&amp;quot;hn hn-angle-right-solid profile-action-arrow&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        var greatWallBox = &#039;&#039;;&lt;br /&gt;
        var siteInformationBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            greatWallBox = buildGreatWallBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;The Great Wall build failed:&#039;, err);&lt;br /&gt;
            greatWallBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            siteInformationBox = buildSiteInformationBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Site information build failed:&#039;, err);&lt;br /&gt;
            siteInformationBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        var sidebar = userBox + greatWallBox + siteInformationBox;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).append(&#039;&amp;lt;div id=&amp;quot;clbi-right-sidebar&amp;quot;&amp;gt;&#039; + sidebar + &#039;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        initGreatWallBoxWhenReady();&lt;br /&gt;
        removeMainPortalGuestbookPreview();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    initGreatWallBoxWhenReady();&lt;br /&gt;
    removeMainPortalGuestbookPreview();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    if ($(&#039;#clbi-left-sidebar&#039;).length === 0) {&lt;br /&gt;
var leftBillboardBox = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            leftBillboardBox = buildRightBillboardBox();&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.error(&#039;Left billboard build failed:&#039;, err);&lt;br /&gt;
            leftBillboardBox = &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
var leftSidebar =&lt;br /&gt;
    &#039;&amp;lt;div id=&amp;quot;clbi-left-sidebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-language&amp;quot;&amp;gt;언어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content sidebar-lang-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selector&amp;quot; class=&amp;quot;sidebar-lang-selector sidebar-lang-dial&amp;quot; tabindex=&amp;quot;0&amp;quot; role=&amp;quot;group&amp;quot; aria-label=&amp;quot;언어 선택&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-dial-stage&amp;quot; class=&amp;quot;sidebar-lang-dial-stage&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-fan&amp;quot; class=&amp;quot;sidebar-lang-fan&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-selected-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-left&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-selected-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;한국어&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div id=&amp;quot;clbi-sidebar-lang-availability-panel&amp;quot; class=&amp;quot;sidebar-lang-status-panel sidebar-lang-status-right is-current&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span id=&amp;quot;clbi-sidebar-lang-availability-value&amp;quot; class=&amp;quot;sidebar-lang-status-value&amp;quot;&amp;gt;CURRENT&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-sidebar-lang-apply&amp;quot; class=&amp;quot;sidebar-lang-apply&amp;quot; aria-label=&amp;quot;언어 적용&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;sidebar-lang-apply-mark&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;✓&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;clbi-left-box clbi-left-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-title&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;span id=&amp;quot;clbi-title-left-news&amp;quot;&amp;gt;뉴스&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-left-content clbi-news-box&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-title&amp;quot;&amp;gt;CHANGELOG&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-changelog-feed&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;a href=&amp;quot;/index.php/체인지로그&amp;quot; class=&amp;quot;news-post-item&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;news-post-title-wrap&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;span class=&amp;quot;news-post-title&amp;quot; id=&amp;quot;clbi-left-news-changelog-main&amp;quot;&amp;gt;체인지로그&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-post-jump&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-divider&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-feed-title&amp;quot; id=&amp;quot;clbi-left-news-recent-title&amp;quot;&amp;gt;RECENT CHANGES&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;news-left-recent-feed&amp;quot; id=&amp;quot;clbi-left-recent-list&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
                &#039;&amp;lt;a class=&amp;quot;news-fill-image-slot&amp;quot; id=&amp;quot;clbi-left-news-fill-image&amp;quot; href=&amp;quot;/index.php/특수:최근바뀜&amp;quot; aria-label=&amp;quot;최근 바뀜으로 이동&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;news-fill-image-frame&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;span class=&amp;quot;news-fill-image&amp;quot; style=&amp;quot;--news-fill-image-url:url(\&#039;/index.php?title=특수:Redirect/file/Side-news-fill-001.png\&#039;);&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;img class=&amp;quot;news-fill-image-probe&amp;quot; src=&amp;quot;/index.php?title=특수:Redirect/file/Side-news-fill-001.png&amp;quot; alt=&amp;quot;&amp;quot; aria-hidden=&amp;quot;true&amp;quot; onerror=&amp;quot;this.onerror=null;this.closest(\&#039;.news-fill-image-slot\&#039;).classList.add(\&#039;is-empty\&#039;);this.remove();&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        leftBillboardBox +&lt;br /&gt;
    &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $(&#039;.content-wrapper&#039;).prepend(leftSidebar);&lt;br /&gt;
&lt;br /&gt;
        renderSidebarLanguageBox();&lt;br /&gt;
        loadRecentChangesList(&#039;#clbi-left-recent-list&#039;, 10);&lt;br /&gt;
        scheduleAdaptiveLeftRecentItems();&lt;br /&gt;
        scheduleLeftBillboardAdaptive();&lt;br /&gt;
        scheduleClbiContentBottomGap();&lt;br /&gt;
        updateLeftSidebarNationsImage();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        initRightBillboardCarousel();&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        console.error(&#039;Right billboard carousel failed:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
    applyMainPageStyle();&lt;br /&gt;
    initClbiCustomDocumentScrollbars();&lt;br /&gt;
    initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
        window.ProgressSystemWebUi.boot(&#039;initSidebars&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initNotifications();&lt;br /&gt;
            initProfile();&lt;br /&gt;
            moveTocToLeftSidebar();&lt;br /&gt;
        }, 300);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 800);&lt;br /&gt;
        setTimeout(moveTocToLeftSidebar, 1500);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    loadLangScript(function() {&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            initSidebars();&lt;br /&gt;
        }, 100);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click.profileQuickPlaceholder&#039;, &#039;#profile-quick-inventory, #profile-quick-achievements&#039;, function(e) {&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
function extractJsonArrayAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var depth = 0;&lt;br /&gt;
    var inString = false;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = String(text || &#039;&#039;).indexOf(&#039;[&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (inString) {&lt;br /&gt;
            if (escaped) {&lt;br /&gt;
                escaped = false;&lt;br /&gt;
            } else if (ch === &#039;\\&#039;) {&lt;br /&gt;
                escaped = true;&lt;br /&gt;
            } else if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
                inString = false;&lt;br /&gt;
            }&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            inString = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;[&#039;) depth += 1;&lt;br /&gt;
        if (ch === &#039;]&#039;) {&lt;br /&gt;
            depth -= 1;&lt;br /&gt;
            if (depth === 0) {&lt;br /&gt;
                try {&lt;br /&gt;
                    return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
                } catch (err) {&lt;br /&gt;
                    return null;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function extractJsonStringAfterMwConfigKey(text, key) {&lt;br /&gt;
    var needle = &#039;&amp;quot;&#039; + key + &#039;&amp;quot;&#039;;&lt;br /&gt;
    var keyIndex = String(text || &#039;&#039;).indexOf(needle);&lt;br /&gt;
    var colon;&lt;br /&gt;
    var start;&lt;br /&gt;
    var i;&lt;br /&gt;
    var escaped = false;&lt;br /&gt;
&lt;br /&gt;
    if (keyIndex === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    colon = text.indexOf(&#039;:&#039;, keyIndex + needle.length);&lt;br /&gt;
    if (colon === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    start = text.indexOf(&#039;&amp;quot;&#039;, colon + 1);&lt;br /&gt;
    if (start === -1) return null;&lt;br /&gt;
&lt;br /&gt;
    for (i = start + 1; i &amp;lt; text.length; i += 1) {&lt;br /&gt;
        var ch = text.charAt(i);&lt;br /&gt;
&lt;br /&gt;
        if (escaped) {&lt;br /&gt;
            escaped = false;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;\\&#039;) {&lt;br /&gt;
            escaped = true;&lt;br /&gt;
            continue;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (ch === &#039;&amp;quot;&#039;) {&lt;br /&gt;
            try {&lt;br /&gt;
                return JSON.parse(text.slice(start, i + 1));&lt;br /&gt;
            } catch (err) {&lt;br /&gt;
                return text.slice(start + 1, i);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function syncCatlinksConfigFromSpaDocument(doc) {&lt;br /&gt;
    var scripts = doc ? doc.querySelectorAll(&#039;script&#039;) : [];&lt;br /&gt;
    var categories = null;&lt;br /&gt;
    var hiddenCategories = null;&lt;br /&gt;
    var relevantPageName = null;&lt;br /&gt;
    var pageName = null;&lt;br /&gt;
    var i;&lt;br /&gt;
    var text;&lt;br /&gt;
    var value;&lt;br /&gt;
&lt;br /&gt;
    for (i = 0; i &amp;lt; scripts.length; i += 1) {&lt;br /&gt;
        text = scripts[i].textContent || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (categories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) categories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (hiddenCategories === null) {&lt;br /&gt;
            value = extractJsonArrayAfterMwConfigKey(text, &#039;wgHiddenCategories&#039;);&lt;br /&gt;
            if (Array.isArray(value)) hiddenCategories = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (relevantPageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgRelevantPageName&#039;);&lt;br /&gt;
            if (value !== null) relevantPageName = value;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (pageName === null) {&lt;br /&gt;
            value = extractJsonStringAfterMwConfigKey(text, &#039;wgPageName&#039;);&lt;br /&gt;
            if (value !== null) pageName = value;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    mw.config.set(&#039;wgCategories&#039;, Array.isArray(categories) ? categories : []);&lt;br /&gt;
    mw.config.set(&#039;wgHiddenCategories&#039;, Array.isArray(hiddenCategories) ? hiddenCategories : []);&lt;br /&gt;
&lt;br /&gt;
    if (relevantPageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, relevantPageName);&lt;br /&gt;
    } else if (pageName !== null) {&lt;br /&gt;
        mw.config.set(&#039;wgRelevantPageName&#039;, pageName);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    CLBI_CATLINKS_FETCH_TOKEN += 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// SPA 네비게이션&lt;br /&gt;
function shouldSkip(url) {&lt;br /&gt;
    return url.match(/action=edit|action=submit|action=history|action=delete|action=protect|action=purge|특수:로그인|특수:로그아웃|Special:UserLogin|Special:UserLogout|특수:사용자정보|특수:비밀번호바꾸기|uselang=/);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function() {&lt;br /&gt;
    if (window._spaInitialized) return;&lt;br /&gt;
    window._spaInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    function isInternal(url) {&lt;br /&gt;
        var a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = url;&lt;br /&gt;
        return a.hostname === window.location.hostname;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getCachedSpaPageHtml(url) {&lt;br /&gt;
        if (!window.EntryStore || typeof window.EntryStore.getTextSync !== &#039;function&#039;) return &#039;&#039;;&lt;br /&gt;
        return window.EntryStore.getTextSync(url) || window.EntryStore.getTextSync(String(url || &#039;&#039;).replace(/^https?:\/\/[^/]+/i, &#039;&#039;)) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchSpaPageHtml(url) {&lt;br /&gt;
        var cached = getCachedSpaPageHtml(url);&lt;br /&gt;
        if (cached) return Promise.resolve(cached);&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; }).then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function prepareDetachedEntryContent(newContent) {&lt;br /&gt;
        /*&lt;br /&gt;
        Initial boot prepares entry artifacts; SPA is only allowed to consume them.&lt;br /&gt;
        This hook runs while the fetched page is still detached, before the user sees it.&lt;br /&gt;
        It must stay synchronous or already-resolved: if a subsystem cannot prepare from&lt;br /&gt;
        EntryStore immediately, it should leave the old fallback path in place instead of&lt;br /&gt;
        opening BootGate during SPA.&lt;br /&gt;
        */&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.NationsPanel &amp;amp;&amp;amp; typeof window.NationsPanel.prepareContentForEntry === &#039;function&#039;) {&lt;br /&gt;
                window.NationsPanel.prepareContentForEntry(newContent);&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;entry content preparation failed:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function loadPage(url) {&lt;br /&gt;
        invalidateProfileRender();&lt;br /&gt;
&lt;br /&gt;
        return fetchSpaPageHtml(url)&lt;br /&gt;
            .then(function(html) {&lt;br /&gt;
                var parser = new DOMParser();&lt;br /&gt;
                var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
                var scripts = doc.querySelectorAll(&#039;script&#039;);&lt;br /&gt;
                for (var i = 0; i &amp;lt; scripts.length; i++) {&lt;br /&gt;
                    var src = scripts[i].textContent;&lt;br /&gt;
&lt;br /&gt;
                    if (src.indexOf(&#039;wgNamespaceNumber&#039;) !== -1) {&lt;br /&gt;
                        var match = src.match(/&amp;quot;wgNamespaceNumber&amp;quot;:(-?\d+)/);&lt;br /&gt;
                        if (match) mw.config.set(&#039;wgNamespaceNumber&#039;, parseInt(match[1], 10));&lt;br /&gt;
&lt;br /&gt;
                        var matchTitle = src.match(/&amp;quot;wgTitle&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchTitle) mw.config.set(&#039;wgTitle&#039;, matchTitle[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchPage = src.match(/&amp;quot;wgPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchPage) mw.config.set(&#039;wgPageName&#039;, matchPage[1]);&lt;br /&gt;
&lt;br /&gt;
                        var matchArticle = src.match(/&amp;quot;wgArticleId&amp;quot;:(\d+)/);&lt;br /&gt;
                        if (matchArticle) {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, parseInt(matchArticle[1], 10));&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgArticleId&#039;, 0);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchIsMainPage = src.match(/&amp;quot;wgIsMainPage&amp;quot;:(true|false)/);&lt;br /&gt;
                        if (matchIsMainPage) {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, matchIsMainPage[1] === &#039;true&#039;);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgIsMainPage&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
&lt;br /&gt;
                        var matchSpecial = src.match(/&amp;quot;wgCanonicalSpecialPageName&amp;quot;:&amp;quot;([^&amp;quot;]+)&amp;quot;/);&lt;br /&gt;
                        if (matchSpecial) {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, matchSpecial[1]);&lt;br /&gt;
                        } else {&lt;br /&gt;
                            mw.config.set(&#039;wgCanonicalSpecialPageName&#039;, false);&lt;br /&gt;
                        }&lt;br /&gt;
                        break;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                syncCatlinksConfigFromSpaDocument(doc);&lt;br /&gt;
&lt;br /&gt;
                var newContent = doc.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
                var newTitle = doc.querySelector(&#039;.mw-page-title-main&#039;);&lt;br /&gt;
                var newHead = doc.querySelector(&#039;title&#039;);&lt;br /&gt;
                var newHeader = doc.querySelector(&#039;.liberty-content-header&#039;);&lt;br /&gt;
&lt;br /&gt;
                if (newContent) {&lt;br /&gt;
                    prepareDetachedEntryContent(newContent);&lt;br /&gt;
                    prepareSpaCatlinksBeforeInsert(newContent);&lt;br /&gt;
                    $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
                    $(&#039;.liberty-content-main&#039;).html(newContent.innerHTML);&lt;br /&gt;
                    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
                    try {&lt;br /&gt;
                        if (window.Decorations &amp;amp;&amp;amp; typeof window.Decorations.renderPrepared === &#039;function&#039;) window.Decorations.renderPrepared();&lt;br /&gt;
                        else if (window.CLBI_DECORATIONS &amp;amp;&amp;amp; typeof window.CLBI_DECORATIONS.renderPrepared === &#039;function&#039;) window.CLBI_DECORATIONS.renderPrepared();&lt;br /&gt;
                    } catch (err) {}&lt;br /&gt;
                    $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newTitle) {&lt;br /&gt;
                    $(&#039;.mw-page-title-main&#039;).html(newTitle.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHead) {&lt;br /&gt;
                    document.title = newHead.textContent;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (newHeader) {&lt;br /&gt;
                    $(&#039;.liberty-content-header&#039;).html(newHeader.innerHTML);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (typeof window.normalizeClbiShellDomOrder === &#039;function&#039;) window.normalizeClbiShellDomOrder();&lt;br /&gt;
                window.scrollTo(0, 0);&lt;br /&gt;
                mw.hook(&#039;wikipage.content&#039;).fire($(&#039;.liberty-content-main&#039;));&lt;br /&gt;
                applyMainPageStyle();&lt;br /&gt;
                initClbiCustomDocumentScrollbars();&lt;br /&gt;
                initCategoryNavIfAvailable(document);&lt;br /&gt;
&lt;br /&gt;
                if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.handleSpaPageView === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.handleSpaPageView();&lt;br /&gt;
                } else if (window.ProgressSystemWebUi &amp;amp;&amp;amp; typeof window.ProgressSystemWebUi.boot === &#039;function&#039;) {&lt;br /&gt;
                    window.ProgressSystemWebUi.boot(&#039;spa&#039;);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                $(&#039;#side-toc-box&#039;).remove();&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 100);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 500);&lt;br /&gt;
                setTimeout(moveTocToLeftSidebar, 1200);&lt;br /&gt;
&lt;br /&gt;
                mw.loader.using([&#039;mediawiki.api&#039;]).then(function() {&lt;br /&gt;
                    initProfile();&lt;br /&gt;
                    moveTocToLeftSidebar();&lt;br /&gt;
                });&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function (err) {&lt;br /&gt;
                console.error(&#039;SPA page load failed:&#039;, err);&lt;br /&gt;
                $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
// 목차 링크는 전용 처리&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;#side-toc-box a, #toc a, .toc a&#039;, function(e) {&lt;br /&gt;
    var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
    if (!href || href.charAt(0) !== &#039;#&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var rawId = href.slice(1);&lt;br /&gt;
    if (!rawId) return;&lt;br /&gt;
&lt;br /&gt;
    var decodedId = rawId;&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        decodedId = decodeURIComponent(rawId);&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
        decodedId = rawId;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var target = document.getElementById(decodedId);&lt;br /&gt;
&lt;br /&gt;
    if (!target &amp;amp;&amp;amp; window.CSS &amp;amp;&amp;amp; CSS.escape) {&lt;br /&gt;
        target = document.querySelector(&#039;#&#039; + CSS.escape(decodedId));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!target) return;&lt;br /&gt;
&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
    e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
    var scrollTarget = target.closest(&#039;h2, h3&#039;) || target;&lt;br /&gt;
&lt;br /&gt;
    scrollTarget.scrollIntoView({&lt;br /&gt;
        behavior: &#039;auto&#039;,&lt;br /&gt;
        block: &#039;start&#039;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    history.replaceState(null, &#039;&#039;, &#039;#&#039; + rawId);&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
    /* 길라잡이는 문서 링크가 아니라 대문 본문 화면 탭이다. */&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;, function(e) {&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        if (window.BottomGuideNav &amp;amp;&amp;amp; typeof window.BottomGuideNav.toggle === &#039;function&#039;) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopImmediatePropagation();&lt;br /&gt;
            window.BottomGuideNav.toggle();&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    $(document).on(&#039;click&#039;, &#039;a&#039;, function(e) {&lt;br /&gt;
        // 휠 클릭, 새 탭 열기, 보조키 이동은 브라우저 기본 동작을 유지한다.&lt;br /&gt;
        if (e.which &amp;amp;&amp;amp; e.which !== 1) return;&lt;br /&gt;
        if (e.button &amp;amp;&amp;amp; e.button !== 0) return;&lt;br /&gt;
        if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;&lt;br /&gt;
&lt;br /&gt;
        /* 길라잡이 화면 탭은 위 전용 처리 외에는 SPA 문서 이동 대상으로 삼지 않는다. */&lt;br /&gt;
        if ($(this).is(&#039;.portal-guide-anchor[data-category-key=&amp;quot;guide&amp;quot;]&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var href = $(this).attr(&#039;href&#039;);&lt;br /&gt;
        if (!href) return;&lt;br /&gt;
&lt;br /&gt;
        // 목차 링크는 별도 핸들러에서 처리&lt;br /&gt;
        if ($(this).closest(&#039;#side-toc-box, #toc, .toc&#039;).length) return;&lt;br /&gt;
&lt;br /&gt;
        // 단순 해시 링크는 SPA 가로채기 제외&lt;br /&gt;
        if (href.startsWith(&#039;#&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        var link = document.createElement(&#039;a&#039;);&lt;br /&gt;
        link.href = href;&lt;br /&gt;
&lt;br /&gt;
        var samePath = decodeURIComponent(link.pathname) === decodeURIComponent(window.location.pathname);&lt;br /&gt;
        var sameSearch = (link.search || &#039;&#039;) === (window.location.search || &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; samePath &amp;amp;&amp;amp; sameSearch) return;&lt;br /&gt;
&lt;br /&gt;
        var currentBase = window.location.href.split(&#039;#&#039;)[0];&lt;br /&gt;
        var targetBase = link.href.split(&#039;#&#039;)[0];&lt;br /&gt;
&lt;br /&gt;
        if (link.hash &amp;amp;&amp;amp; currentBase === targetBase) return;&lt;br /&gt;
&lt;br /&gt;
        if (!isInternal(href)) return;&lt;br /&gt;
        if (shouldSkip(href)) return;&lt;br /&gt;
&lt;br /&gt;
        e.preventDefault();&lt;br /&gt;
        playStaticSound();&lt;br /&gt;
        /*&lt;br /&gt;
        SPA must remain a consumer phase.  If the target page HTML was prepared by&lt;br /&gt;
        the first-load boot pack, do not show the legacy page-loading veil.  The&lt;br /&gt;
        route will consume cached HTML and detached entry artifacts before insertion.&lt;br /&gt;
        */&lt;br /&gt;
        if (getCachedSpaPageHtml(href)) {&lt;br /&gt;
            $(&#039;body&#039;).removeClass(&#039;page-loading&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;body&#039;).addClass(&#039;page-loading&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        history.pushState(null, &#039;&#039;, href);&lt;br /&gt;
        loadPage(href);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;popstate&#039;, function() {&lt;br /&gt;
        loadPage(window.location.href);&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ========== CLBI Custom Document Scrollbar ========== */&lt;br /&gt;
function isGeneralDocumentView() {&lt;br /&gt;
    var body = document.body;&lt;br /&gt;
    if (!body) return false;&lt;br /&gt;
&lt;br /&gt;
    return body.classList.contains(&#039;action-view&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-main-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;clbi-system-doc-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;backend-system-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-page&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
        !body.classList.contains(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiDocumentScrollTargets() {&lt;br /&gt;
    if (!isGeneralDocumentView()) return [];&lt;br /&gt;
&lt;br /&gt;
    return Array.prototype.slice.call(document.querySelectorAll(&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; #mw-content-text .mw-parser-output, &#039; +&lt;br /&gt;
        &#039;.liberty-content-main &amp;gt; .mw-body-content .mw-parser-output&#039;&lt;br /&gt;
    )).filter(function (el, index, list) {&lt;br /&gt;
        return el &amp;amp;&amp;amp; list.indexOf(el) === index;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getClbiOuterWellForScroll(scrollEl) {&lt;br /&gt;
    var main = scrollEl ? scrollEl.closest(&#039;.liberty-content-main&#039;) : null;&lt;br /&gt;
    var children;&lt;br /&gt;
    var i;&lt;br /&gt;
    var child;&lt;br /&gt;
&lt;br /&gt;
    if (!main) return null;&lt;br /&gt;
&lt;br /&gt;
    children = Array.prototype.slice.call(main.children || []);&lt;br /&gt;
    for (i = 0; i &amp;lt; children.length; i += 1) {&lt;br /&gt;
        child = children[i];&lt;br /&gt;
        if (&lt;br /&gt;
            child &amp;amp;&amp;amp;&lt;br /&gt;
            (child.id === &#039;mw-content-text&#039; || child.classList.contains(&#039;mw-body-content&#039;)) &amp;amp;&amp;amp;&lt;br /&gt;
            child.contains(scrollEl)&lt;br /&gt;
        ) {&lt;br /&gt;
            return child;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return scrollEl.parentElement || null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function buildClbiCustomScrollbar(well, scrollEl) {&lt;br /&gt;
    var bar = well.querySelector(&#039;:scope &amp;gt; .clbi-custom-scrollbar&#039;);&lt;br /&gt;
    var up;&lt;br /&gt;
    var track;&lt;br /&gt;
    var thumb;&lt;br /&gt;
    var down;&lt;br /&gt;
&lt;br /&gt;
    if (!bar) {&lt;br /&gt;
        bar = document.createElement(&#039;div&#039;);&lt;br /&gt;
        bar.className = &#039;clbi-custom-scrollbar&#039;;&lt;br /&gt;
        bar.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        bar.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-up&amp;quot; data-scroll-arrow=&amp;quot;up&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-track&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;clbi-custom-scroll-thumb&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;clbi-custom-scroll-arrow clbi-custom-scroll-arrow-down&amp;quot; data-scroll-arrow=&amp;quot;down&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        well.appendChild(bar);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.__clbiScrollTarget = scrollEl;&lt;br /&gt;
    up = bar.querySelector(&#039;.clbi-custom-scroll-arrow-up&#039;);&lt;br /&gt;
    track = bar.querySelector(&#039;.clbi-custom-scroll-track&#039;);&lt;br /&gt;
    thumb = bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;);&lt;br /&gt;
    down = bar.querySelector(&#039;.clbi-custom-scroll-arrow-down&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (up &amp;amp;&amp;amp; !up.__clbiBound) {&lt;br /&gt;
        up.__clbiBound = true;&lt;br /&gt;
        up.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop -= 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (down &amp;amp;&amp;amp; !down.__clbiBound) {&lt;br /&gt;
        down.__clbiBound = true;&lt;br /&gt;
        down.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
            if (bar.__clbiScrollTarget) bar.__clbiScrollTarget.scrollTop += 48;&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (track &amp;amp;&amp;amp; !track.__clbiBound) {&lt;br /&gt;
        track.__clbiBound = true;&lt;br /&gt;
        track.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var rect;&lt;br /&gt;
            var thumbRect;&lt;br /&gt;
            var target;&lt;br /&gt;
            var direction;&lt;br /&gt;
&lt;br /&gt;
            if (e.target === thumb) return;&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            target = bar.__clbiScrollTarget;&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            rect = track.getBoundingClientRect();&lt;br /&gt;
            thumbRect = thumb.getBoundingClientRect();&lt;br /&gt;
            direction = e.clientY &amp;lt; thumbRect.top ? -1 : 1;&lt;br /&gt;
            target.scrollTop += direction * Math.max(60, Math.floor(target.clientHeight * 0.82));&lt;br /&gt;
            updateClbiCustomScrollbar(bar);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (thumb &amp;amp;&amp;amp; !thumb.__clbiBound) {&lt;br /&gt;
        thumb.__clbiBound = true;&lt;br /&gt;
        thumb.addEventListener(&#039;mousedown&#039;, function (e) {&lt;br /&gt;
            var target = bar.__clbiScrollTarget;&lt;br /&gt;
            var startY;&lt;br /&gt;
            var startScroll;&lt;br /&gt;
            var maxScroll;&lt;br /&gt;
            var maxThumbTop;&lt;br /&gt;
            var trackHeight;&lt;br /&gt;
            var thumbHeight;&lt;br /&gt;
&lt;br /&gt;
            if (!target) return;&lt;br /&gt;
&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            startY = e.clientY;&lt;br /&gt;
            startScroll = target.scrollTop;&lt;br /&gt;
            maxScroll = Math.max(1, target.scrollHeight - target.clientHeight);&lt;br /&gt;
            trackHeight = track ? track.clientHeight : 0;&lt;br /&gt;
            thumbHeight = thumb.offsetHeight || 0;&lt;br /&gt;
            maxThumbTop = Math.max(1, trackHeight - thumbHeight);&lt;br /&gt;
&lt;br /&gt;
            bar.classList.add(&#039;is-dragging&#039;);&lt;br /&gt;
&lt;br /&gt;
            function onMove(moveEvent) {&lt;br /&gt;
                var dy = moveEvent.clientY - startY;&lt;br /&gt;
                target.scrollTop = startScroll + (dy / maxThumbTop) * maxScroll;&lt;br /&gt;
                updateClbiCustomScrollbar(bar);&lt;br /&gt;
                moveEvent.preventDefault();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            function onUp() {&lt;br /&gt;
                bar.classList.remove(&#039;is-dragging&#039;);&lt;br /&gt;
                document.removeEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
                document.removeEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            document.addEventListener(&#039;mousemove&#039;, onMove);&lt;br /&gt;
            document.addEventListener(&#039;mouseup&#039;, onUp);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!scrollEl.__clbiCustomScrollbarBound) {&lt;br /&gt;
        scrollEl.__clbiCustomScrollbarBound = true;&lt;br /&gt;
        scrollEl.addEventListener(&#039;scroll&#039;, function () {&lt;br /&gt;
            if (scrollEl.__clbiCustomScrollbar) {&lt;br /&gt;
                updateClbiCustomScrollbar(scrollEl.__clbiCustomScrollbar);&lt;br /&gt;
            }&lt;br /&gt;
        }, { passive: true });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    scrollEl.__clbiCustomScrollbar = bar;&lt;br /&gt;
    updateClbiCustomScrollbar(bar);&lt;br /&gt;
&lt;br /&gt;
    return bar;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateClbiCustomScrollbar(bar) {&lt;br /&gt;
    var scrollEl = bar &amp;amp;&amp;amp; bar.__clbiScrollTarget;&lt;br /&gt;
    var track = bar ? bar.querySelector(&#039;.clbi-custom-scroll-track&#039;) : null;&lt;br /&gt;
    var thumb = bar ? bar.querySelector(&#039;.clbi-custom-scroll-thumb&#039;) : null;&lt;br /&gt;
    var maxScroll;&lt;br /&gt;
    var trackHeight;&lt;br /&gt;
    var thumbHeight;&lt;br /&gt;
    var maxTop;&lt;br /&gt;
    var top;&lt;br /&gt;
&lt;br /&gt;
    if (!bar || !scrollEl || !track || !thumb) return;&lt;br /&gt;
&lt;br /&gt;
    maxScroll = scrollEl.scrollHeight - scrollEl.clientHeight;&lt;br /&gt;
    if (maxScroll &amp;lt;= 1) {&lt;br /&gt;
        bar.classList.add(&#039;is-hidden&#039;);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    bar.classList.remove(&#039;is-hidden&#039;);&lt;br /&gt;
&lt;br /&gt;
    trackHeight = Math.max(1, track.clientHeight || 1);&lt;br /&gt;
    thumbHeight = Math.max(12, Math.floor((scrollEl.clientHeight / Math.max(scrollEl.scrollHeight, 1)) * trackHeight));&lt;br /&gt;
    thumbHeight = Math.min(trackHeight, thumbHeight);&lt;br /&gt;
    maxTop = Math.max(0, trackHeight - thumbHeight);&lt;br /&gt;
    top = maxScroll &amp;gt; 0 ? Math.round((scrollEl.scrollTop / maxScroll) * maxTop) : 0;&lt;br /&gt;
&lt;br /&gt;
    thumb.style.height = thumbHeight + &#039;px&#039;;&lt;br /&gt;
    thumb.style.transform = &#039;translateY(&#039; + top + &#039;px)&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initClbiCustomDocumentScrollbars() {&lt;br /&gt;
    var existing = Array.prototype.slice.call(document.querySelectorAll(&#039;.clbi-custom-scrollbar&#039;));&lt;br /&gt;
    var targets = getClbiDocumentScrollTargets();&lt;br /&gt;
    var liveBars = [];&lt;br /&gt;
&lt;br /&gt;
    if (!targets.length) {&lt;br /&gt;
        existing.forEach(function (bar) { bar.remove(); });&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    targets.forEach(function (scrollEl) {&lt;br /&gt;
        var well = getClbiOuterWellForScroll(scrollEl);&lt;br /&gt;
        var bar;&lt;br /&gt;
&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
        bar = buildClbiCustomScrollbar(well, scrollEl);&lt;br /&gt;
        liveBars.push(bar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    existing.forEach(function (bar) {&lt;br /&gt;
        if (liveBars.indexOf(bar) === -1) bar.remove();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    window.requestAnimationFrame(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        liveBars.forEach(updateClbiCustomScrollbar);&lt;br /&gt;
    }, 120);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (!window.__clbiCustomScrollbarResizeBound) {&lt;br /&gt;
    window.__clbiCustomScrollbarResizeBound = true;&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        setTimeout(initClbiCustomDocumentScrollbars, 60);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// 시간 계산 함수&lt;br /&gt;
function timeAgo(timestamp) {&lt;br /&gt;
    var now = new Date();&lt;br /&gt;
    var date = new Date(timestamp);&lt;br /&gt;
    var diff = Math.floor((now - date) / 1000);&lt;br /&gt;
&lt;br /&gt;
    if (diff &amp;lt; 60) return diff + &#039;초 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 3600) return Math.floor(diff / 60) + &#039;분 전&#039;;&lt;br /&gt;
    if (diff &amp;lt; 86400) return Math.floor(diff / 3600) + &#039;시간 전&#039;;&lt;br /&gt;
    return Math.floor(diff / 86400) + &#039;일 전&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
// 펼접 토글&lt;br /&gt;
function getFoldTexts() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return (window.LANG &amp;amp;&amp;amp; window.LANG[lang])&lt;br /&gt;
        ? window.LANG[lang]&lt;br /&gt;
        : (window.LANG ? window.LANG.ko : { expand: &#039;펼치기&#039;, collapse: &#039;접기&#039; });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function refreshOpenAncestors($start) {&lt;br /&gt;
    $start.parents(&#039;[id^=&amp;quot;collapsible&amp;quot;]&#039;).each(function () {&lt;br /&gt;
        var $parent = $(this);&lt;br /&gt;
        if (!$parent.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        // 이미 fully open 상태면 굳이 다시 잠그지 않음&lt;br /&gt;
        if ($parent.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $parent.css(&#039;max-height&#039;, this.scrollHeight + &#039;px&#039;);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function bindInnerResizeUpdates($target) {&lt;br /&gt;
    // 이미지 늦게 로드될 때 높이 갱신&lt;br /&gt;
    $target.find(&#039;img&#039;).off(&#039;.foldimg&#039;).on(&#039;load.foldimg&#039;, function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
            if ($target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
                $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function openFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;opening&#039;);&lt;br /&gt;
    $target.addClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    // 열린 뒤 자연 확장 가능하게 만들기 위해 먼저 px로 열기&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.collapse);&lt;br /&gt;
&lt;br /&gt;
    bindInnerResizeUpdates($target);&lt;br /&gt;
&lt;br /&gt;
    // 바깥 펼접 즉시 갱신&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    // 전환 끝나면 none으로 풀어서 중첩 펼접/동적 내용 증가를 자연스럽게 허용&lt;br /&gt;
    $target.off(&#039;transitionend.foldopen&#039;).on(&#039;transitionend.foldopen&#039;, function (e) {&lt;br /&gt;
        if (e.target !== this) return;&lt;br /&gt;
        if (!$target.hasClass(&#039;folding-open&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        $target.css(&#039;max-height&#039;, &#039;none&#039;);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;open&#039;);&lt;br /&gt;
&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // 늦게 렌더되는 콘텐츠 대응&lt;br /&gt;
    requestAnimationFrame(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 80);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        if ($target.hasClass(&#039;folding-open&#039;) &amp;amp;&amp;amp; $target.data(&#039;fold-state&#039;) !== &#039;open&#039;) {&lt;br /&gt;
            $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
            refreshOpenAncestors($target);&lt;br /&gt;
        }&lt;br /&gt;
    }, 220);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function closeFold($target, $btn) {&lt;br /&gt;
    var t = getFoldTexts();&lt;br /&gt;
&lt;br /&gt;
    // none 상태에서 닫으면 transition이 안 되므로 실제 높이로 고정&lt;br /&gt;
    if ($target.css(&#039;max-height&#039;) === &#039;none&#039; || $target.data(&#039;fold-state&#039;) === &#039;open&#039;) {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    } else {&lt;br /&gt;
        $target.css(&#039;max-height&#039;, $target[0].scrollHeight + &#039;px&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $target.data(&#039;fold-state&#039;, &#039;closing&#039;);&lt;br /&gt;
    $target[0].offsetHeight;&lt;br /&gt;
    $target.css(&#039;max-height&#039;, &#039;0px&#039;);&lt;br /&gt;
    $target.removeClass(&#039;folding-open&#039;);&lt;br /&gt;
&lt;br /&gt;
    $btn.text(t.expand);&lt;br /&gt;
&lt;br /&gt;
    refreshOpenAncestors($target);&lt;br /&gt;
&lt;br /&gt;
    setTimeout(function () {&lt;br /&gt;
        refreshOpenAncestors($target);&lt;br /&gt;
        $target.data(&#039;fold-state&#039;, &#039;closed&#039;);&lt;br /&gt;
    }, 250);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$(function () {&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiToggle&#039;, &#039;.toggleBtn&#039;, function () {&lt;br /&gt;
            var $btn = $(this);&lt;br /&gt;
            var targetId = $btn.data(&#039;target&#039;);&lt;br /&gt;
            var $target = $(&#039;#&#039; + targetId);&lt;br /&gt;
            if (!$target.length) return;&lt;br /&gt;
&lt;br /&gt;
            var scrollY = window.scrollY;&lt;br /&gt;
&lt;br /&gt;
            if ($target.hasClass(&#039;folding-open&#039;)) {&lt;br /&gt;
                closeFold($target, $btn);&lt;br /&gt;
            } else {&lt;br /&gt;
                openFold($target, $btn);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            window.scrollTo(0, scrollY);&lt;br /&gt;
        });&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
// ========== 프로필 시스템 ==========&lt;br /&gt;
function initProfile() {&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    $(&#039;.user-profile-portal&#039;).removeClass(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var token = ++PROFILE_RENDER_TOKEN;&lt;br /&gt;
    var ns = mw.config.get(&#039;wgNamespaceNumber&#039;);&lt;br /&gt;
    var title = mw.config.get(&#039;wgTitle&#039;);&lt;br /&gt;
    var specialPage = mw.config.get(&#039;wgCanonicalSpecialPageName&#039;);&lt;br /&gt;
    var isProfileSettings = specialPage === &#039;사용자정보&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-page&#039;, ns === 2);&lt;br /&gt;
    $(&#039;body&#039;).toggleClass(&#039;user-profile-settings-page&#039;, isProfileSettings);&lt;br /&gt;
&lt;br /&gt;
    if (ns === 2) {&lt;br /&gt;
        var profileUser = title.split(&#039;/&#039;)[0];&lt;br /&gt;
        renderProfile(profileUser, token);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (isProfileSettings) {&lt;br /&gt;
        initUserProfilePage();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderProfile(username, token) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;query&#039;,&lt;br /&gt;
        list: &#039;users&#039;,&lt;br /&gt;
        ususers: username,&lt;br /&gt;
        usprop: &#039;editcount&#039;&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        if (token !== PROFILE_RENDER_TOKEN) return;&lt;br /&gt;
        if (mw.config.get(&#039;wgNamespaceNumber&#039;) !== 2) return;&lt;br /&gt;
&lt;br /&gt;
        var currentTitle = String(mw.config.get(&#039;wgTitle&#039;) || &#039;&#039;).split(&#039;/&#039;)[0];&lt;br /&gt;
        if (currentTitle !== username) return;&lt;br /&gt;
&lt;br /&gt;
        var user = data.query.users[0];&lt;br /&gt;
        var contentEl = document.getElementById(&#039;mw-content-text&#039;);&lt;br /&gt;
        if (!contentEl) return;&lt;br /&gt;
&lt;br /&gt;
        var pageContent = contentEl.querySelector(&#039;.mw-parser-output&#039;) || contentEl;&lt;br /&gt;
        injectProfileCard(username, user, pageContent);&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function injectProfileCard(username, userData, container) {&lt;br /&gt;
    var isOwnPage = mw.config.get(&#039;wgUserName&#039;) === username;&lt;br /&gt;
    var editCount = (userData &amp;amp;&amp;amp; userData.editcount) ? userData.editcount : 0;&lt;br /&gt;
&lt;br /&gt;
    function escapeHtml(value) {&lt;br /&gt;
        return String(value == null ? &#039;&#039; : value)&lt;br /&gt;
            .replace(/&amp;amp;/g, &#039;&amp;amp;amp;&#039;)&lt;br /&gt;
            .replace(/&amp;lt;/g, &#039;&amp;amp;lt;&#039;)&lt;br /&gt;
            .replace(/&amp;gt;/g, &#039;&amp;amp;gt;&#039;)&lt;br /&gt;
            .replace(/&amp;quot;/g, &#039;&amp;amp;quot;&#039;)&lt;br /&gt;
            .replace(/&#039;/g, &#039;&amp;amp;#039;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    container.classList.add(&#039;user-profile-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
    var safeUsername = escapeHtml(username);&lt;br /&gt;
    var avatarSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-&#039; + encodeURIComponent(username) + &#039;.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var fallbackSrc = &#039;/index.php?title=특수:Redirect/file/Pfp-default.png&amp;amp;width=220&#039;;&lt;br /&gt;
    var editBtn = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;a href=&amp;quot;/index.php/특수:사용자정보&amp;quot; class=&amp;quot;profile-edit-btn&amp;quot;&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-label&amp;quot;&amp;gt;프로필 수정&amp;lt;/span&amp;gt;&amp;lt;span class=&amp;quot;profile-edit-arrow&amp;quot;&amp;gt;›&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var progressHtml = isOwnPage&lt;br /&gt;
        ? &#039;&amp;lt;div class=&amp;quot;profile-page-progress is-syncing&amp;quot; data-profile-progress&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;LEVEL RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-page-progress-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-level&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;profile-page-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;div class=&amp;quot;profile-page-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-sub&amp;quot;&amp;gt;SYNCING&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-page-progress-meta&amp;quot;&amp;gt;TODAY — · DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;&lt;br /&gt;
        : &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
    var card = document.createElement(&#039;div&#039;);&lt;br /&gt;
    card.className = &#039;profile-card profile-page-console&#039;;&lt;br /&gt;
    card.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-titlebar&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;USER PROFILE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;OFFICIAL ARCHIVE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div class=&amp;quot;profile-card-body&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-identity-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-avatar-bay&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img src=&amp;quot;&#039; + avatarSrc + &#039;&amp;quot; onerror=&amp;quot;this.onerror=null;this.src=\&#039;&#039; + fallbackSrc + &#039;\&#039;;&amp;quot; alt=&amp;quot;&#039; + safeUsername + &#039;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-info-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-nameplate&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;h2 class=&amp;quot;profile-username&amp;quot;&amp;gt;&#039; + safeUsername + &#039;&amp;lt;/h2&amp;gt;&#039; +&lt;br /&gt;
                        editBtn +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-name&amp;quot; data-field=&amp;quot;name&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-role&amp;quot; data-field=&amp;quot;role&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-discord&amp;quot; data-field=&amp;quot;discord&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;profile-lower-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-bio-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;BIOGRAPHY&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-bio&amp;quot; data-field=&amp;quot;bio&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;profile-stats-panel&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-section-title&amp;quot;&amp;gt;RECORD&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;profile-stats&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                        progressHtml +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-stat-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot;&amp;gt;&#039; + editCount + &#039;&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;수정 횟수&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-stat&amp;quot; data-contrib-pages-stat&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-value&amp;quot; data-contrib-pages-value&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;clbi-stat-label&amp;quot;&amp;gt;기여 문서&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;div class=&amp;quot;profile-info-grid&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;TIME&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-time&amp;gt;UTC --:--&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;div class=&amp;quot;profile-info-stat&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-label&amp;quot;&amp;gt;LANGUAGE&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                                &#039;&amp;lt;span class=&amp;quot;profile-info-value&amp;quot; data-profile-language&amp;gt;—&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.profile-card&#039;).remove();&lt;br /&gt;
    container.insertBefore(card, container.firstChild);&lt;br /&gt;
    loadProfileFields(username, card);&lt;br /&gt;
    loadProfileContributionPages(username, card);&lt;br /&gt;
    updateProfilePageEnvironment(card, null);&lt;br /&gt;
&lt;br /&gt;
    if (isOwnPage) {&lt;br /&gt;
        loadProfileProgressForUserPage(card);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function getProfileLanguageLabel() {&lt;br /&gt;
    var lang = getCurrentLang();&lt;br /&gt;
    return SIDEBAR_LANGUAGE_LABELS[lang] || (lang ? lang.toUpperCase() : &#039;—&#039;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfilePageEnvironment(card, summary) {&lt;br /&gt;
    if (!card) return;&lt;br /&gt;
&lt;br /&gt;
    var timezone = summary &amp;amp;&amp;amp; summary.timezone ? summary.timezone : &#039;UTC&#039;;&lt;br /&gt;
    var timeEl = card.querySelector(&#039;[data-profile-time]&#039;);&lt;br /&gt;
    var langEl = card.querySelector(&#039;[data-profile-language]&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (timeEl) {&lt;br /&gt;
        try {&lt;br /&gt;
            timeEl.textContent = timezone + &#039; &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: timezone&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            timeEl.textContent = &#039;UTC &#039; + new Intl.DateTimeFormat(&#039;ko-KR&#039;, {&lt;br /&gt;
                hour: &#039;2-digit&#039;,&lt;br /&gt;
                minute: &#039;2-digit&#039;,&lt;br /&gt;
                hour12: false,&lt;br /&gt;
                timeZone: &#039;UTC&#039;&lt;br /&gt;
            }).format(new Date());&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (langEl) {&lt;br /&gt;
        langEl.textContent = getProfileLanguageLabel();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileContributionPages(username, card) {&lt;br /&gt;
    if (!username || !card) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    var valueEl = card.querySelector(&#039;[data-contrib-pages-value]&#039;);&lt;br /&gt;
    if (valueEl) valueEl.textContent = &#039;SYNC&#039;;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        var pages = Object.create(null);&lt;br /&gt;
        var cont = {};&lt;br /&gt;
        var guard = 0;&lt;br /&gt;
&lt;br /&gt;
        function requestNext() {&lt;br /&gt;
            guard++;&lt;br /&gt;
&lt;br /&gt;
            var params = Object.assign({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                list: &#039;usercontribs&#039;,&lt;br /&gt;
                ucuser: username,&lt;br /&gt;
                ucnamespace: 0,&lt;br /&gt;
                ucprop: &#039;title&#039;,&lt;br /&gt;
                uclimit: &#039;max&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }, cont);&lt;br /&gt;
&lt;br /&gt;
            return api.get(params).then(function (data) {&lt;br /&gt;
                var rows = data &amp;amp;&amp;amp; data.query &amp;amp;&amp;amp; data.query.usercontribs ? data.query.usercontribs : [];&lt;br /&gt;
&lt;br /&gt;
                rows.forEach(function (row) {&lt;br /&gt;
                    if (row &amp;amp;&amp;amp; row.title) pages[row.title] = true;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                if (data &amp;amp;&amp;amp; data.continue &amp;amp;&amp;amp; data.continue.uccontinue &amp;amp;&amp;amp; guard &amp;lt; 40) {&lt;br /&gt;
                    cont = data.continue;&lt;br /&gt;
                    return requestNext();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (valueEl) valueEl.textContent = Object.keys(pages).length;&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        requestNext().fail(function () {&lt;br /&gt;
            if (valueEl) valueEl.textContent = &#039;—&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileProgressForUserPage(card) {&lt;br /&gt;
    if (!mw.config.get(&#039;wgUserName&#039;)) return;&lt;br /&gt;
    if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
        var api = new mw.Api();&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;progress_summary&#039;,&lt;br /&gt;
            format: &#039;json&#039;,&lt;br /&gt;
            formatversion: 2&lt;br /&gt;
        }).then(function (data) {&lt;br /&gt;
            var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
            if (!payload || !payload.available || !payload.summary) return;&lt;br /&gt;
            updateUserPageProgress(card, payload.summary);&lt;br /&gt;
            updateProfilePageEnvironment(card, payload.summary);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateUserPageProgress(card, summary) {&lt;br /&gt;
    var panel = card.querySelector(&#039;[data-profile-progress]&#039;);&lt;br /&gt;
    if (!panel || !summary) return;&lt;br /&gt;
&lt;br /&gt;
    var level = summary.level || 1;&lt;br /&gt;
    var totalXp = summary.totalXp || 0;&lt;br /&gt;
    var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
    var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
    var percent = Math.max(0, Math.min(100, summary.progressPercent || 0));&lt;br /&gt;
    var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
    var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
    var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
&lt;br /&gt;
    panel.classList.remove(&#039;is-syncing&#039;);&lt;br /&gt;
    panel.classList.toggle(&#039;is-max-level&#039;, isMaxLevel);&lt;br /&gt;
&lt;br /&gt;
    var levelEl = panel.querySelector(&#039;.profile-page-level&#039;);&lt;br /&gt;
    var totalEl = panel.querySelector(&#039;.profile-page-total-xp&#039;);&lt;br /&gt;
    var fillEl = panel.querySelector(&#039;.profile-page-xp-fill&#039;);&lt;br /&gt;
    var subEl = panel.querySelector(&#039;.profile-page-progress-sub&#039;);&lt;br /&gt;
    var metaEl = panel.querySelector(&#039;.profile-page-progress-meta&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (levelEl) levelEl.textContent = (isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level;&lt;br /&gt;
    if (totalEl) totalEl.textContent = totalXp + &#039; XP&#039;;&lt;br /&gt;
    if (fillEl) fillEl.style.width = percent + &#039;%&#039;;&lt;br /&gt;
    if (subEl) subEl.textContent = isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;);&lt;br /&gt;
    if (metaEl) metaEl.textContent = &#039;TODAY &#039; + dailyXp + &#039; XP · DISCOVERED &#039; + discoveries;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadProfileFields(username, card) {&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    api.get({&lt;br /&gt;
        action: &#039;userprofile&#039;,&lt;br /&gt;
        user: username&lt;br /&gt;
    }).then(function(data) {&lt;br /&gt;
        var profile = data.userprofile;&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: profile.name || &#039;&#039;,&lt;br /&gt;
            discord: profile.discord || &#039;&#039;,&lt;br /&gt;
            role: profile.role || &#039;&#039;,&lt;br /&gt;
            bio: profile.bio || &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    }).fail(function() {&lt;br /&gt;
        updateProfileFields(card, {&lt;br /&gt;
            name: &#039;&#039;,&lt;br /&gt;
            discord: &#039;&#039;,&lt;br /&gt;
            role: &#039;&#039;,&lt;br /&gt;
            bio: &#039;&#039;&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function updateProfileFields(card, data) {&lt;br /&gt;
    var nameEl = card.querySelector(&#039;[data-field=&amp;quot;name&amp;quot;]&#039;);&lt;br /&gt;
    var roleEl = card.querySelector(&#039;[data-field=&amp;quot;role&amp;quot;]&#039;);&lt;br /&gt;
    var discordEl = card.querySelector(&#039;[data-field=&amp;quot;discord&amp;quot;]&#039;);&lt;br /&gt;
    var bioEl = card.querySelector(&#039;[data-field=&amp;quot;bio&amp;quot;]&#039;);&lt;br /&gt;
    if (nameEl) nameEl.textContent = data.name || &#039;&#039;;&lt;br /&gt;
    if (roleEl) roleEl.textContent = data.role || &#039;&#039;;&lt;br /&gt;
    if (discordEl) discordEl.textContent = data.discord ? (&#039;디스코드: &#039; + data.discord) : &#039;&#039;;&lt;br /&gt;
    if (bioEl) bioEl.textContent = data.bio || &#039;&#039;;&lt;br /&gt;
}&lt;br /&gt;
// ========== 프로필 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
// ========== 알림 시스템 ==========&lt;br /&gt;
function ensureNotificationPopup() {&lt;br /&gt;
    if (document.getElementById(&#039;clbi-notification-popup&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
    var popup = document.createElement(&#039;div&#039;);&lt;br /&gt;
    popup.id = &#039;clbi-notification-popup&#039;;&lt;br /&gt;
    popup.style.cssText =&lt;br /&gt;
        &#039;display:none;position:fixed;z-index:99999;width:320px;max-height:420px;&#039; +&lt;br /&gt;
        &#039;background:#0a0909;border:2px solid #854369;border-radius:5px;&#039; +&lt;br /&gt;
        &#039;box-shadow:0 0 0 1px #1a1a1a, 0 8px 24px rgba(0,0,0,0.55);overflow:hidden;&#039;;&lt;br /&gt;
&lt;br /&gt;
    popup.innerHTML =&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:10px 12px;border-bottom:2px solid #854369;background:linear-gradient(to bottom, #171114 0%, #0a0909 100%);color:#E2E2E2;font-size:13px;font-weight:700;display:flex;align-items:center;justify-content:space-between;gap:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;span&amp;gt;알림&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-notification-readall&amp;quot; style=&amp;quot;background:#171717;border:1px solid #854369;border-radius:6px;color:#E2E2E2;font-size:11px;font-weight:700;padding:4px 8px;cursor:pointer;&amp;quot;&amp;gt;전체 읽음&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div id=&amp;quot;clbi-notification-list&amp;quot; style=&amp;quot;max-height:320px;overflow-y:auto;padding:8px 0;color:#E2E2E2;font-size:12px;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;div style=&amp;quot;padding:8px;border-top:1px solid #2a2a2a;background:#111;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;/index.php?title=Special:Notifications&amp;quot; id=&amp;quot;clbi-notification-more&amp;quot; style=&amp;quot;display:block;width:100%;text-align:center;padding:8px 10px;border-radius:6px;background:#171717;border:1px solid #854369;color:#E2E2E2 !important;text-decoration:none !important;font-size:12px;font-weight:700;&amp;quot;&amp;gt;더보기&amp;lt;/a&amp;gt;&#039; +&lt;br /&gt;
        &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
    document.body.appendChild(popup);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function positionNotificationPopup() {&lt;br /&gt;
    var btn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
    var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
    if (!btn || !popup) return;&lt;br /&gt;
&lt;br /&gt;
    var rect = btn.getBoundingClientRect();&lt;br /&gt;
    var top = rect.bottom + 6;&lt;br /&gt;
    var left = rect.left + (rect.width / 2) - (popup.offsetWidth / 2);&lt;br /&gt;
&lt;br /&gt;
    if (left &amp;lt; 8) left = 8;&lt;br /&gt;
    if (left + popup.offsetWidth &amp;gt; window.innerWidth - 8) {&lt;br /&gt;
        left = window.innerWidth - popup.offsetWidth - 8;&lt;br /&gt;
    }&lt;br /&gt;
    if (top + popup.offsetHeight &amp;gt; window.innerHeight - 8) {&lt;br /&gt;
        top = Math.max(8, rect.top - popup.offsetHeight - 6);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    popup.style.top = top + &#039;px&#039;;&lt;br /&gt;
    popup.style.left = left + &#039;px&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function parseNotificationItemsFromHtml(html) {&lt;br /&gt;
    var parser = new DOMParser();&lt;br /&gt;
    var doc = parser.parseFromString(html, &#039;text/html&#039;);&lt;br /&gt;
&lt;br /&gt;
    var selectors = [&lt;br /&gt;
        &#039;.mw-echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;.mw-echo-ui-notificationsInboxWidgetRow&#039;,&lt;br /&gt;
        &#039;.echo-ui-notificationItemWidget&#039;,&lt;br /&gt;
        &#039;li[data-notification-id]&#039;,&lt;br /&gt;
        &#039;.mw-echo-notifications-list li&#039;&lt;br /&gt;
    ];&lt;br /&gt;
&lt;br /&gt;
    var items = [];&lt;br /&gt;
    for (var i = 0; i &amp;lt; selectors.length; i++) {&lt;br /&gt;
        items = Array.prototype.slice.call(doc.querySelectorAll(selectors[i]));&lt;br /&gt;
        if (items.length) break;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return items.slice(0, 5).map(function(item) {&lt;br /&gt;
        var link = item.querySelector(&#039;a[href]&#039;);&lt;br /&gt;
        var href = link ? link.getAttribute(&#039;href&#039;) : &#039;/index.php?title=Special:Notifications&#039;;&lt;br /&gt;
        var text = (item.textContent || &#039;&#039;).replace(/\s+/g, &#039; &#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        var notificationId =&lt;br /&gt;
            item.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
            item.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
            &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (!notificationId) {&lt;br /&gt;
            var anyWithId = item.querySelector(&#039;[data-notification-id], [data-id], [data-notification]&#039;);&lt;br /&gt;
            if (anyWithId) {&lt;br /&gt;
                notificationId =&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-id&#039;) ||&lt;br /&gt;
                    anyWithId.getAttribute(&#039;data-notification&#039;) ||&lt;br /&gt;
                    &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (href &amp;amp;&amp;amp; href.indexOf(&#039;http&#039;) !== 0) {&lt;br /&gt;
            href = href.charAt(0) === &#039;/&#039;&lt;br /&gt;
                ? href&lt;br /&gt;
                : &#039;/index.php&#039; + (href.charAt(0) === &#039;?&#039; ? href : &#039;/&#039; + href);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            id: notificationId,&lt;br /&gt;
            href: href,&lt;br /&gt;
            text: text || &#039;알림&#039;&lt;br /&gt;
        };&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function setNotificationIcon(hasItems) {&lt;br /&gt;
    var quickIcon = document.getElementById(&#039;profile-quick-notification-icon&#039;);&lt;br /&gt;
    var svg = hasItems ? CLBI_SVG_BELL_DOT : CLBI_SVG_BELL;&lt;br /&gt;
&lt;br /&gt;
    if (quickIcon) {&lt;br /&gt;
        quickIcon.innerHTML = svg;&lt;br /&gt;
        quickIcon.classList.toggle(&#039;has-notifications&#039;, !!hasItems);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function renderNotificationPopup(items) {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    var badge = document.getElementById(&#039;clbi-notification-badge&#039;);&lt;br /&gt;
    if (!list) return;&lt;br /&gt;
&lt;br /&gt;
    if (!items || !items.length) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;표시할 알림이 없습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        if (badge) badge.style.display = &#039;none&#039;;&lt;br /&gt;
        setNotificationIcon(false);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var html = &#039;&#039;;&lt;br /&gt;
    for (var i = 0; i &amp;lt; items.length; i++) {&lt;br /&gt;
        html +=&lt;br /&gt;
            &#039;&amp;lt;a href=&amp;quot;&#039; + items[i].href + &#039;&amp;quot; class=&amp;quot;clbi-notification-item&amp;quot; data-notification-id=&amp;quot;&#039; + (items[i].id || &#039;&#039;) + &#039;&amp;quot; style=&amp;quot;display:block;padding:10px 12px;color:#E2E2E2 !important;text-decoration:none !important;border-bottom:1px solid #1f1f1f;line-height:1.5;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                items[i].text +&lt;br /&gt;
            &#039;&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    list.innerHTML = html;&lt;br /&gt;
&lt;br /&gt;
    if (badge) {&lt;br /&gt;
        badge.textContent = items.length;&lt;br /&gt;
        badge.style.display = &#039;block&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    setNotificationIcon(true);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function loadNotificationsIntoPopup() {&lt;br /&gt;
    var list = document.getElementById(&#039;clbi-notification-list&#039;);&lt;br /&gt;
    if (list) {&lt;br /&gt;
        list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;불러오는 중...&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    fetch(&#039;/index.php?title=Special:Notifications&#039;, { credentials: &#039;same-origin&#039; })&lt;br /&gt;
        .then(function(res) {&lt;br /&gt;
            return res.text();&lt;br /&gt;
        })&lt;br /&gt;
        .then(function(html) {&lt;br /&gt;
            var items = parseNotificationItemsFromHtml(html);&lt;br /&gt;
            renderNotificationPopup(items);&lt;br /&gt;
        })&lt;br /&gt;
        .catch(function(err) {&lt;br /&gt;
            console.error(err);&lt;br /&gt;
            if (list) {&lt;br /&gt;
                list.innerHTML = &#039;&amp;lt;div style=&amp;quot;padding:14px 12px;color:#999;&amp;quot;&amp;gt;알림을 불러오지 못했습니다.&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markAllNotificationsRead() {&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: &#039;all&#039;&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function markNotificationReadById(notificationId) {&lt;br /&gt;
    if (!notificationId) {&lt;br /&gt;
        return $.Deferred().resolve().promise();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return new mw.Api().postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
        action: &#039;echomarkread&#039;,&lt;br /&gt;
        list: notificationId&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function initNotifications() {&lt;br /&gt;
    var quickBtn = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!quickBtn) return;&lt;br /&gt;
&lt;br /&gt;
    ensureNotificationPopup();&lt;br /&gt;
    loadNotificationsIntoPopup();&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationToggle&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationToggle&#039;, &#039;#profile-quick-notifications&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (popup.style.display === &#039;none&#039; || popup.style.display === &#039;&#039;) {&lt;br /&gt;
                popup.style.display = &#039;block&#039;;&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
            } else {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationOutside&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationOutside&#039;, function(e) {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            var quickToggle = document.getElementById(&#039;profile-quick-notifications&#039;);&lt;br /&gt;
            if (!popup) return;&lt;br /&gt;
&lt;br /&gt;
            if (!popup.contains(e.target) &amp;amp;&amp;amp; (!quickToggle || !quickToggle.contains(e.target))) {&lt;br /&gt;
                popup.style.display = &#039;none&#039;;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationReadAll&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationReadAll&#039;, &#039;#clbi-notification-readall&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var button = this;&lt;br /&gt;
            button.disabled = true;&lt;br /&gt;
            button.textContent = &#039;처리 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            markAllNotificationsRead()&lt;br /&gt;
                .then(function() {&lt;br /&gt;
                    loadNotificationsIntoPopup();&lt;br /&gt;
                })&lt;br /&gt;
                .always(function() {&lt;br /&gt;
                    button.disabled = false;&lt;br /&gt;
                    button.textContent = &#039;전체 읽음&#039;;&lt;br /&gt;
                });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(document)&lt;br /&gt;
        .off(&#039;click.clbiNotificationItem&#039;)&lt;br /&gt;
        .on(&#039;click.clbiNotificationItem&#039;, &#039;.clbi-notification-item&#039;, function(e) {&lt;br /&gt;
            e.preventDefault();&lt;br /&gt;
            e.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
            var href = this.getAttribute(&#039;href&#039;);&lt;br /&gt;
            var notificationId = this.getAttribute(&#039;data-notification-id&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            markNotificationReadById(notificationId).always(function() {&lt;br /&gt;
                loadNotificationsIntoPopup();&lt;br /&gt;
                if (href) {&lt;br /&gt;
                    window.location.href = href;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    $(window)&lt;br /&gt;
        .off(&#039;resize.clbiNotification&#039;)&lt;br /&gt;
        .on(&#039;resize.clbiNotification&#039;, function() {&lt;br /&gt;
            var popup = document.getElementById(&#039;clbi-notification-popup&#039;);&lt;br /&gt;
            if (popup &amp;amp;&amp;amp; popup.style.display === &#039;block&#039;) {&lt;br /&gt;
                positionNotificationPopup();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
}&lt;br /&gt;
// ========== 알림 시스템 끝 ==========&lt;br /&gt;
&lt;br /&gt;
function initUserProfilePage() {&lt;br /&gt;
    $(&#039;body&#039;).addClass(&#039;user-profile-settings-page&#039;);&lt;br /&gt;
&lt;br /&gt;
    var saveBtn = document.getElementById(&#039;pref-save&#039;);&lt;br /&gt;
    if (!saveBtn) return;&lt;br /&gt;
&lt;br /&gt;
    function getPrefRow(id) {&lt;br /&gt;
        var el = document.getElementById(id);&lt;br /&gt;
        if (!el) return null;&lt;br /&gt;
        return el.closest(&#039;.clbi-pref-row&#039;) || el.parentNode;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function removePrefRow(id) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (row &amp;amp;&amp;amp; row.parentNode) {&lt;br /&gt;
            row.parentNode.removeChild(row);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createPrefSection(className, titleText) {&lt;br /&gt;
        var section = document.createElement(&#039;div&#039;);&lt;br /&gt;
        section.className = &#039;clbi-pref-section &#039; + className;&lt;br /&gt;
&lt;br /&gt;
        var title = document.createElement(&#039;div&#039;);&lt;br /&gt;
        title.className = &#039;clbi-pref-section-title&#039;;&lt;br /&gt;
        title.textContent = titleText;&lt;br /&gt;
&lt;br /&gt;
        var body = document.createElement(&#039;div&#039;);&lt;br /&gt;
        body.className = &#039;clbi-pref-section-body&#039;;&lt;br /&gt;
&lt;br /&gt;
        section.appendChild(title);&lt;br /&gt;
        section.appendChild(body);&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            section: section,&lt;br /&gt;
            body: body&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveRowToSection(id, targetBody, className) {&lt;br /&gt;
        var row = getPrefRow(id);&lt;br /&gt;
        if (!row || !targetBody) return false;&lt;br /&gt;
&lt;br /&gt;
        row.classList.add(&#039;clbi-pref-row-key-&#039; + className);&lt;br /&gt;
        targetBody.appendChild(row);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebuildProfileSettingsLayout() {&lt;br /&gt;
        var root = document.querySelector(&#039;.clbi-prefs-profile&#039;);&lt;br /&gt;
        if (!root || root.dataset.profileSettingsReworked === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        root.dataset.profileSettingsReworked = &#039;1&#039;;&lt;br /&gt;
        root.classList.add(&#039;profile-settings-console&#039;);&lt;br /&gt;
&lt;br /&gt;
        removePrefRow(&#039;pref-badges&#039;);&lt;br /&gt;
&lt;br /&gt;
        var originalRows = Array.prototype.slice.call(root.querySelectorAll(&#039;.clbi-pref-row&#039;));&lt;br /&gt;
        var actionNodes = [];&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn.parentNode === root || saveBtn.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(saveBtn);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var statusNode = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        if (statusNode &amp;amp;&amp;amp; statusNode.closest(&#039;.clbi-prefs-profile&#039;) === root) {&lt;br /&gt;
            actionNodes.push(statusNode);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var main = document.createElement(&#039;div&#039;);&lt;br /&gt;
        main.className = &#039;clbi-pref-main-grid&#039;;&lt;br /&gt;
&lt;br /&gt;
        var media = createPrefSection(&#039;clbi-pref-section-media&#039;, &#039;PROFILE IMAGE&#039;);&lt;br /&gt;
        var identity = createPrefSection(&#039;clbi-pref-section-identity&#039;, &#039;IDENTITY RECORD&#039;);&lt;br /&gt;
        var bio = createPrefSection(&#039;clbi-pref-section-bio&#039;, &#039;BIOGRAPHY&#039;);&lt;br /&gt;
        var account = createPrefSection(&#039;clbi-pref-section-account&#039;, &#039;ACCOUNT CONTACT&#039;);&lt;br /&gt;
        var misc = createPrefSection(&#039;clbi-pref-section-misc&#039;, &#039;OTHER OPTIONS&#039;);&lt;br /&gt;
&lt;br /&gt;
        main.appendChild(media.section);&lt;br /&gt;
        main.appendChild(identity.section);&lt;br /&gt;
        main.appendChild(bio.section);&lt;br /&gt;
        main.appendChild(account.section);&lt;br /&gt;
        main.appendChild(misc.section);&lt;br /&gt;
&lt;br /&gt;
        root.innerHTML = &#039;&#039;;&lt;br /&gt;
        root.appendChild(main);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-preview&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-btn&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-pfp-input&#039;, media.body, &#039;pfp&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-name&#039;, identity.body, &#039;name&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-role&#039;, identity.body, &#039;role&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-discord&#039;, identity.body, &#039;discord&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-bio&#039;, bio.body, &#039;bio&#039;);&lt;br /&gt;
&lt;br /&gt;
        moveRowToSection(&#039;pref-new-email&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-password&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
        moveRowToSection(&#039;pref-email-save&#039;, account.body, &#039;email&#039;);&lt;br /&gt;
&lt;br /&gt;
        originalRows.forEach(function (row) {&lt;br /&gt;
            if (!row.parentNode &amp;amp;&amp;amp; !row.className.match(/clbi-pref-row-key-/)) {&lt;br /&gt;
                misc.body.appendChild(row);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!misc.body.children.length) {&lt;br /&gt;
            misc.section.parentNode.removeChild(misc.section);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var actions = document.createElement(&#039;div&#039;);&lt;br /&gt;
        actions.className = &#039;clbi-pref-actions&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (saveBtn) actions.appendChild(saveBtn);&lt;br /&gt;
        if (statusNode) actions.appendChild(statusNode);&lt;br /&gt;
&lt;br /&gt;
        root.appendChild(actions);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    rebuildProfileSettingsLayout();&lt;br /&gt;
&lt;br /&gt;
    var api = new mw.Api();&lt;br /&gt;
    var selectedFile = null;&lt;br /&gt;
    var cropper = null;&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-gallery-modal&#039;)) {&lt;br /&gt;
        var gModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        gModal.id = &#039;clbi-gallery-modal&#039;;&lt;br /&gt;
        gModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        gModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:480px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;justify-content:space-between;align-items:center;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;프로필 사진 선택&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-close&amp;quot; style=&amp;quot;background:none;border:none;color:#aaa;font-size:18px;cursor:pointer;&amp;quot;&amp;gt;✕&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-gallery-upload-btn&amp;quot; style=&amp;quot;background:#2a2a2a;border:2px dashed #854369;border-radius:8px;padding:32px;color:#e2e2e2;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:8px;font-size:13px;width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span style=&amp;quot;font-size:32px;&amp;quot;&amp;gt;🖼️&amp;lt;/span&amp;gt;새 사진 업로드&#039; +&lt;br /&gt;
                &#039;&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history-section&amp;quot; style=&amp;quot;display:none;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div style=&amp;quot;font-size:11px;color:#888;margin-bottom:8px;&amp;quot;&amp;gt;이전 사진 — 클릭하면 바로 적용&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div id=&amp;quot;clbi-gallery-history&amp;quot; style=&amp;quot;display:flex;gap:8px;flex-wrap:wrap;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(gModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (!document.getElementById(&#039;clbi-crop-modal&#039;)) {&lt;br /&gt;
        var cModal = document.createElement(&#039;div&#039;);&lt;br /&gt;
        cModal.id = &#039;clbi-crop-modal&#039;;&lt;br /&gt;
        cModal.style.cssText =&lt;br /&gt;
            &#039;display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);z-index:99999;align-items:center;justify-content:center;&#039;;&lt;br /&gt;
&lt;br /&gt;
        cModal.innerHTML =&lt;br /&gt;
            &#039;&amp;lt;div style=&amp;quot;background:#1e1e1e;border:2px solid #854369;border-radius:12px;padding:24px;max-width:500px;width:90%;display:flex;flex-direction:column;gap:16px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;font-size:14px;font-weight:700;color:#e2e2e2;&amp;quot;&amp;gt;사진 조정&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;width:100%;max-height:380px;overflow:hidden;border-radius:8px;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;img id=&amp;quot;clbi-crop-image&amp;quot; style=&amp;quot;max-width:100%;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div style=&amp;quot;display:flex;gap:8px;justify-content:flex-end;&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-cancel&amp;quot; style=&amp;quot;background:#2a2a2a;color:#e2e2e2;border:1px solid #444;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;취소&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;button type=&amp;quot;button&amp;quot; id=&amp;quot;clbi-crop-confirm&amp;quot; style=&amp;quot;background:#854369;color:#fff;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;&amp;quot;&amp;gt;확정&amp;lt;/button&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
&lt;br /&gt;
        document.body.appendChild(cModal);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var gModal = document.getElementById(&#039;clbi-gallery-modal&#039;);&lt;br /&gt;
    var cModal = document.getElementById(&#039;clbi-crop-modal&#039;);&lt;br /&gt;
    var cropImage = document.getElementById(&#039;clbi-crop-image&#039;);&lt;br /&gt;
    var pfpInput = document.getElementById(&#039;pref-pfp-input&#039;);&lt;br /&gt;
&lt;br /&gt;
    function openGallery() {&lt;br /&gt;
        gModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
        api.get({&lt;br /&gt;
            action: &#039;query&#039;,&lt;br /&gt;
            titles: &#039;파일:Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
            prop: &#039;imageinfo&#039;,&lt;br /&gt;
            iiprop: &#039;url|timestamp&#039;,&lt;br /&gt;
            iilimit: 6&lt;br /&gt;
        }).then(function(data) {&lt;br /&gt;
            var pages = data.query.pages;&lt;br /&gt;
            var page = pages[Object.keys(pages)[0]];&lt;br /&gt;
            if (!page.imageinfo || page.imageinfo.length === 0) return;&lt;br /&gt;
&lt;br /&gt;
            var historyEl = document.getElementById(&#039;clbi-gallery-history&#039;);&lt;br /&gt;
            var sectionEl = document.getElementById(&#039;clbi-gallery-history-section&#039;);&lt;br /&gt;
            historyEl.innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            page.imageinfo.forEach(function(info, idx) {&lt;br /&gt;
                var wrap = document.createElement(&#039;div&#039;);&lt;br /&gt;
                wrap.style.cssText = &#039;position:relative;cursor:pointer;&#039;;&lt;br /&gt;
&lt;br /&gt;
                var img = document.createElement(&#039;img&#039;);&lt;br /&gt;
                img.src = info.url;&lt;br /&gt;
                img.style.cssText =&lt;br /&gt;
                    &#039;width:72px;height:72px;object-fit:cover;border-radius:8px;border:2px solid #444;flex-shrink:0;&#039;;&lt;br /&gt;
&lt;br /&gt;
                if (idx === 0) {&lt;br /&gt;
                    img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                    var badge = document.createElement(&#039;div&#039;);&lt;br /&gt;
                    badge.textContent = &#039;현재&#039;;&lt;br /&gt;
                    badge.style.cssText =&lt;br /&gt;
                        &#039;position:absolute;bottom:4px;left:50%;transform:translateX(-50%);background:#854369;color:#fff;font-size:9px;padding:1px 6px;border-radius:10px;&#039;;&lt;br /&gt;
                    wrap.appendChild(badge);&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseenter&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#854369&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;mouseleave&#039;, function() {&lt;br /&gt;
                    if (idx !== 0) img.style.borderColor = &#039;#444&#039;;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                img.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
                    fetch(info.url)&lt;br /&gt;
                        .then(function(r) {&lt;br /&gt;
                            return r.blob();&lt;br /&gt;
                        })&lt;br /&gt;
                        .then(function(blob) {&lt;br /&gt;
                            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
                            gModal.style.display = &#039;none&#039;;&lt;br /&gt;
                            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
                        });&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                wrap.appendChild(img);&lt;br /&gt;
                historyEl.appendChild(wrap);&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            sectionEl.style.display = &#039;block&#039;;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function openCrop(src) {&lt;br /&gt;
        cropImage.src = src;&lt;br /&gt;
        cModal.style.display = &#039;flex&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function() {&lt;br /&gt;
            cropper = new Cropper(cropImage, {&lt;br /&gt;
                aspectRatio: 1,&lt;br /&gt;
                viewMode: 1,&lt;br /&gt;
                dragMode: &#039;move&#039;,&lt;br /&gt;
                autoCropArea: 0.8,&lt;br /&gt;
                cropBoxResizable: true,&lt;br /&gt;
                cropBoxMovable: true&lt;br /&gt;
            });&lt;br /&gt;
        }, 150);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;pref-pfp-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        openGallery();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-upload-btn&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        pfpInput.click();&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-gallery-close&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    pfpInput.addEventListener(&#039;change&#039;, function() {&lt;br /&gt;
        var file = this.files[0];&lt;br /&gt;
        if (!file) return;&lt;br /&gt;
&lt;br /&gt;
        gModal.style.display = &#039;none&#039;;&lt;br /&gt;
&lt;br /&gt;
        var reader = new FileReader();&lt;br /&gt;
        reader.onload = function(e) {&lt;br /&gt;
            openCrop(e.target.result);&lt;br /&gt;
        };&lt;br /&gt;
        reader.readAsDataURL(file);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-cancel&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        cModal.style.display = &#039;none&#039;;&lt;br /&gt;
        if (cropper) {&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
        }&lt;br /&gt;
        pfpInput.value = &#039;&#039;;&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    document.getElementById(&#039;clbi-crop-confirm&#039;).addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        if (!cropper) return;&lt;br /&gt;
&lt;br /&gt;
        var canvas = cropper.getCroppedCanvas({ width: 256, height: 256 });&lt;br /&gt;
        if (!canvas) return;&lt;br /&gt;
&lt;br /&gt;
        canvas.toBlob(function(blob) {&lt;br /&gt;
            selectedFile = new File([blob], &#039;profile.png&#039;, { type: &#039;image/png&#039; });&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-preview&#039;).src = URL.createObjectURL(blob);&lt;br /&gt;
            cModal.style.display = &#039;none&#039;;&lt;br /&gt;
            cropper.destroy();&lt;br /&gt;
            cropper = null;&lt;br /&gt;
            document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;✓ 사진 선택됨&#039;;&lt;br /&gt;
        }, &#039;image/png&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    var emailSaveBtn = document.getElementById(&#039;pref-email-save&#039;);&lt;br /&gt;
    if (emailSaveBtn) {&lt;br /&gt;
        emailSaveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
            var statusEl = document.getElementById(&#039;pref-email-status&#039;);&lt;br /&gt;
            var newEmail = document.getElementById(&#039;pref-new-email&#039;).value;&lt;br /&gt;
            var password = document.getElementById(&#039;pref-email-password&#039;).value;&lt;br /&gt;
&lt;br /&gt;
            if (!newEmail || !password) {&lt;br /&gt;
                statusEl.textContent = &#039;이메일과 비밀번호를 입력해주세요.&#039;;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            statusEl.textContent = &#039;변경 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;changeemail&#039;,&lt;br /&gt;
                email: newEmail,&lt;br /&gt;
                password: password&lt;br /&gt;
            }).then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 이메일 변경됨&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-new-email&#039;).value = &#039;&#039;;&lt;br /&gt;
                document.getElementById(&#039;pref-email-password&#039;).value = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 3000);&lt;br /&gt;
            }).fail(function(code, data) {&lt;br /&gt;
                var msg = data &amp;amp;&amp;amp; data.error &amp;amp;&amp;amp; data.error.info ? data.error.info : &#039;변경 실패&#039;;&lt;br /&gt;
                statusEl.textContent = msg;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    saveBtn.addEventListener(&#039;click&#039;, function() {&lt;br /&gt;
        var statusEl = document.getElementById(&#039;pref-status&#039;);&lt;br /&gt;
        statusEl.textContent = &#039;저장 중...&#039;;&lt;br /&gt;
&lt;br /&gt;
        var promises = [];&lt;br /&gt;
&lt;br /&gt;
        if (selectedFile) {&lt;br /&gt;
            var username = mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;upload&#039;,&lt;br /&gt;
                    filename: &#039;Pfp-&#039; + username + &#039;.png&#039;,&lt;br /&gt;
                    ignorewarnings: true,&lt;br /&gt;
                    file: selectedFile,&lt;br /&gt;
                    format: &#039;json&#039;&lt;br /&gt;
                }, {&lt;br /&gt;
                    contentType: &#039;multipart/form-data&#039;&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var fields = [&#039;name&#039;, &#039;discord&#039;, &#039;role&#039;, &#039;bio&#039;];&lt;br /&gt;
&lt;br /&gt;
        for (var i = 0; i &amp;lt; fields.length; i++) {&lt;br /&gt;
            var el = document.getElementById(&#039;pref-&#039; + fields[i]);&lt;br /&gt;
            if (!el) continue;&lt;br /&gt;
&lt;br /&gt;
            promises.push(&lt;br /&gt;
                api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                    action: &#039;options&#039;,&lt;br /&gt;
                    optionname: &#039;profile-&#039; + fields[i],&lt;br /&gt;
                    optionvalue: el.value&lt;br /&gt;
                })&lt;br /&gt;
            );&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $.when.apply($, promises)&lt;br /&gt;
            .then(function() {&lt;br /&gt;
                statusEl.textContent = &#039;✓ 저장됨&#039;;&lt;br /&gt;
                selectedFile = null;&lt;br /&gt;
                document.getElementById(&#039;pref-pfp-btn&#039;).textContent = &#039;사진 선택&#039;;&lt;br /&gt;
&lt;br /&gt;
                setTimeout(function() {&lt;br /&gt;
                    statusEl.textContent = &#039;&#039;;&lt;br /&gt;
                }, 2000);&lt;br /&gt;
            })&lt;br /&gt;
            .fail(function() {&lt;br /&gt;
                statusEl.textContent = &#039;저장 실패&#039;;&lt;br /&gt;
            });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Banner / CRT Page Monitor thumbnail slices&lt;br /&gt;
   - base 이미지는 틀에 들어간 파일 문법 그대로 사용&lt;br /&gt;
   - slice 레이어에는 300px MediaWiki 썸네일만 삽입&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function ($, mw) {&lt;br /&gt;
    var thumbCache = {};&lt;br /&gt;
&lt;br /&gt;
    function parseSliceWidth(value) {&lt;br /&gt;
        var parsed = parseInt(value, 10);&lt;br /&gt;
&lt;br /&gt;
        if (!isFinite(parsed) || parsed &amp;lt; 120) {&lt;br /&gt;
            return 300;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return parsed;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getImageSrc(img) {&lt;br /&gt;
        return img ? (img.currentSrc || img.getAttribute(&#039;src&#039;) || img.src || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getFileNameFromSrc(src) {&lt;br /&gt;
        var a;&lt;br /&gt;
        var parts;&lt;br /&gt;
        var fileName;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        a = document.createElement(&#039;a&#039;);&lt;br /&gt;
        a.href = src;&lt;br /&gt;
&lt;br /&gt;
        parts = (a.pathname || &#039;&#039;).split(&#039;/&#039;).filter(function (part) {&lt;br /&gt;
            return !!part;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (!parts.length) return &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        fileName = parts.pop();&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
         * MediaWiki thumb URL 예시:&lt;br /&gt;
         * /images/thumb/a/ab/File.png/1000px-File.png&lt;br /&gt;
         * /images/thumb/a/ab/File.svg/1000px-File.svg.png&lt;br /&gt;
         *&lt;br /&gt;
         * 이 경우 실제 파일명은 마지막 조각이 아니라 그 앞 조각이다.&lt;br /&gt;
         */&lt;br /&gt;
        if (/^\d+px-/.test(fileName) &amp;amp;&amp;amp; parts.length) {&lt;br /&gt;
            fileName = parts.pop();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        fileName = fileName.replace(/^\d+px-/, &#039;&#039;);&lt;br /&gt;
&lt;br /&gt;
        try {&lt;br /&gt;
            fileName = decodeURIComponent(fileName);&lt;br /&gt;
        } catch (e) {}&lt;br /&gt;
&lt;br /&gt;
        return fileName;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveThumbUrl(img, width, callback) {&lt;br /&gt;
        var src = getImageSrc(img);&lt;br /&gt;
        var fileName = getFileNameFromSrc(src);&lt;br /&gt;
        var cacheKey;&lt;br /&gt;
        var entry;&lt;br /&gt;
&lt;br /&gt;
        if (!src) return;&lt;br /&gt;
&lt;br /&gt;
        if (!fileName || !mw || !mw.loader) {&lt;br /&gt;
            callback(src);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        cacheKey = fileName + &#039;|&#039; + width;&lt;br /&gt;
        entry = thumbCache[cacheKey];&lt;br /&gt;
&lt;br /&gt;
        if (entry) {&lt;br /&gt;
            if (entry.resolved) {&lt;br /&gt;
                callback(entry.url || src);&lt;br /&gt;
            } else {&lt;br /&gt;
                entry.callbacks.push(callback);&lt;br /&gt;
            }&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        entry = {&lt;br /&gt;
            resolved: false,&lt;br /&gt;
            url: &#039;&#039;,&lt;br /&gt;
            callbacks: [callback]&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        thumbCache[cacheKey] = entry;&lt;br /&gt;
&lt;br /&gt;
        function finish(url) {&lt;br /&gt;
            var callbacks = entry.callbacks.slice();&lt;br /&gt;
            var i;&lt;br /&gt;
&lt;br /&gt;
            entry.resolved = true;&lt;br /&gt;
            entry.url = url || src;&lt;br /&gt;
            entry.callbacks = [];&lt;br /&gt;
&lt;br /&gt;
            for (i = 0; i &amp;lt; callbacks.length; i++) {&lt;br /&gt;
                callbacks[i](entry.url);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using(&#039;mediawiki.api&#039;).done(function () {&lt;br /&gt;
            var api = new mw.Api();&lt;br /&gt;
&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;query&#039;,&lt;br /&gt;
                titles: &#039;File:&#039; + fileName,&lt;br /&gt;
                prop: &#039;imageinfo&#039;,&lt;br /&gt;
                iiprop: &#039;url&#039;,&lt;br /&gt;
                iiurlwidth: width,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).done(function (data) {&lt;br /&gt;
                var page;&lt;br /&gt;
                var info;&lt;br /&gt;
&lt;br /&gt;
                if (&lt;br /&gt;
                    data &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages &amp;amp;&amp;amp;&lt;br /&gt;
                    data.query.pages.length&lt;br /&gt;
                ) {&lt;br /&gt;
                    page = data.query.pages[0];&lt;br /&gt;
&lt;br /&gt;
                    if (&lt;br /&gt;
                        page &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo &amp;amp;&amp;amp;&lt;br /&gt;
                        page.imageinfo.length&lt;br /&gt;
                    ) {&lt;br /&gt;
                        info = page.imageinfo[0];&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                finish((info &amp;amp;&amp;amp; (info.thumburl || info.url)) || src);&lt;br /&gt;
            }).fail(function () {&lt;br /&gt;
                finish(src);&lt;br /&gt;
            });&lt;br /&gt;
        }).fail(function () {&lt;br /&gt;
            finish(src);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySliceImages(frame, thumbUrl) {&lt;br /&gt;
        var slices;&lt;br /&gt;
        var i;&lt;br /&gt;
        var img;&lt;br /&gt;
&lt;br /&gt;
        if (!frame || !thumbUrl) return;&lt;br /&gt;
&lt;br /&gt;
        slices = frame.querySelectorAll(&#039;.crt-page-monitor-slice&#039;);&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; slices.length; i++) {&lt;br /&gt;
            slices[i].innerHTML = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
            img = document.createElement(&#039;img&#039;);&lt;br /&gt;
            img.className = &#039;crt-page-monitor-slice-img&#039;;&lt;br /&gt;
            img.src = thumbUrl;&lt;br /&gt;
            img.alt = &#039;&#039;;&lt;br /&gt;
            img.decoding = &#039;async&#039;;&lt;br /&gt;
            img.loading = &#039;eager&#039;;&lt;br /&gt;
            img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
&lt;br /&gt;
            slices[i].appendChild(img);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        frame.setAttribute(&#039;data-crt-slices-ready&#039;, &#039;1&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrame(frame) {&lt;br /&gt;
        var baseImg;&lt;br /&gt;
        var width;&lt;br /&gt;
&lt;br /&gt;
        if (!frame) return;&lt;br /&gt;
        if (frame.getAttribute(&#039;data-crt-slices-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
        baseImg = frame.querySelector(&#039;.crt-page-monitor-image-base img&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!baseImg) return;&lt;br /&gt;
&lt;br /&gt;
        width = parseSliceWidth(frame.getAttribute(&#039;data-crt-slice-width&#039;));&lt;br /&gt;
&lt;br /&gt;
        resolveThumbUrl(baseImg, width, function (thumbUrl) {&lt;br /&gt;
            if (!frame || !frame.parentNode) return;&lt;br /&gt;
            applySliceImages(frame, thumbUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initBannerFrames(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var frames = scope.querySelectorAll(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; frames.length; i++) {&lt;br /&gt;
            initBannerFrame(frames[i]);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initBannerFrames(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initBannerFrames($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(jQuery, window.mw);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Tab System — tab switching UI&lt;br /&gt;
   글리치 플리커 + RGB split + 방향 슬라이드&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function initDocTabs() {&lt;br /&gt;
        var tabBars = document.querySelectorAll(&#039;.doc-tab-bar&#039;);&lt;br /&gt;
        if (!tabBars.length) return;&lt;br /&gt;
&lt;br /&gt;
        tabBars.forEach(function (bar) {&lt;br /&gt;
            if (bar.getAttribute(&#039;data-tabs-init&#039;)) return;&lt;br /&gt;
            bar.setAttribute(&#039;data-tabs-init&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            var tabs = Array.from(bar.querySelectorAll(&#039;.doc-tab&#039;));&lt;br /&gt;
            if (!tabs.length) return;&lt;br /&gt;
&lt;br /&gt;
            var panel = bar.closest(&#039;.doc-panel&#039;);&lt;br /&gt;
            var display = panel ? panel.querySelector(&#039;.doc-display&#039;) : null;&lt;br /&gt;
            if (!display) display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
            if (!display) return;&lt;br /&gt;
&lt;br /&gt;
            tabs.forEach(function (tab, i) {&lt;br /&gt;
                tab.addEventListener(&#039;click&#039;, function () {&lt;br /&gt;
                    var currentIdx = tabs.findIndex(function (t) {&lt;br /&gt;
                        return t.classList.contains(&#039;active&#039;);&lt;br /&gt;
                    });&lt;br /&gt;
                    if (currentIdx === i) return;&lt;br /&gt;
                    switchTab(tabs, display, i, i &amp;gt; currentIdx ? 1 : -1);&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            var initIdx = tabs.findIndex(function (t) { return t.classList.contains(&#039;active&#039;); });&lt;br /&gt;
            if (initIdx !== -1) {&lt;br /&gt;
                var initRef = tabs[initIdx].dataset.ref;&lt;br /&gt;
                var initEl = initRef ? document.getElementById(initRef) : null;&lt;br /&gt;
                display.innerHTML = initEl ? initEl.innerHTML : (tabs[initIdx].dataset.content || &#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var isAnimating = false;&lt;br /&gt;
&lt;br /&gt;
    function switchTab(tabs, display, nextIdx, dir) {&lt;br /&gt;
        if (isAnimating) return;&lt;br /&gt;
        isAnimating = true;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (t) { t.classList.remove(&#039;active&#039;); });&lt;br /&gt;
        tabs[nextIdx].classList.add(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
        var ref = tabs[nextIdx].dataset.ref;&lt;br /&gt;
        var nextContent;&lt;br /&gt;
        if (ref) {&lt;br /&gt;
            var refEl = document.getElementById(ref);&lt;br /&gt;
            nextContent = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
        } else {&lt;br /&gt;
            nextContent = tabs[nextIdx].dataset.content || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        glitchOut(display, dir, function () {&lt;br /&gt;
            display.innerHTML = nextContent;&lt;br /&gt;
            glitchIn(display, dir, function () {&lt;br /&gt;
                isAnimating = false;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchOut(el, dir, cb) {&lt;br /&gt;
        var duration = 160;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var slideX = dir * 16;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = p * p;&lt;br /&gt;
&lt;br /&gt;
            var tx = slideX * ease;&lt;br /&gt;
            var skew = dir * ease * 1.0;&lt;br /&gt;
            var opacity = 1 - ease;&lt;br /&gt;
            var rgb = ease * 5;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.75)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.65)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + (1 + ease * 0.25) + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.opacity = &#039;0&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function glitchIn(el, dir, cb) {&lt;br /&gt;
        var duration = 200;&lt;br /&gt;
        var start = null;&lt;br /&gt;
        var startX = -dir * 16;&lt;br /&gt;
&lt;br /&gt;
        el.style.transform = &#039;translateX(&#039; + startX + &#039;px) skewX(&#039; + (-dir * 1.0) + &#039;deg)&#039;;&lt;br /&gt;
        el.style.opacity = &#039;0&#039;;&lt;br /&gt;
&lt;br /&gt;
        function step(ts) {&lt;br /&gt;
            if (!start) start = ts;&lt;br /&gt;
            var p = Math.min((ts - start) / duration, 1);&lt;br /&gt;
            var ease = 1 - Math.pow(1 - p, 3);&lt;br /&gt;
&lt;br /&gt;
            var tx = startX * (1 - ease);&lt;br /&gt;
            var skew = -dir * 1.0 * (1 - ease);&lt;br /&gt;
            var opacity = ease;&lt;br /&gt;
            var rgb = (1 - ease) * 3;&lt;br /&gt;
            var brightness = 1 + (1 - ease) * 0.35;&lt;br /&gt;
&lt;br /&gt;
            el.style.transform = &#039;translateX(&#039; + tx + &#039;px) skewX(&#039; + skew + &#039;deg)&#039;;&lt;br /&gt;
            el.style.opacity = opacity;&lt;br /&gt;
            el.style.filter =&lt;br /&gt;
                &#039;drop-shadow(&#039; + (-rgb) + &#039;px 0 0 rgba(80,160,255,0.65)) &#039; +&lt;br /&gt;
                &#039;drop-shadow(&#039; + rgb + &#039;px 0 0 rgba(255,55,90,0.55)) &#039; +&lt;br /&gt;
                &#039;brightness(&#039; + brightness + &#039;)&#039;;&lt;br /&gt;
&lt;br /&gt;
            if (p &amp;lt; 1) {&lt;br /&gt;
                requestAnimationFrame(step);&lt;br /&gt;
            } else {&lt;br /&gt;
                el.style.transform = &#039;&#039;;&lt;br /&gt;
                el.style.opacity = &#039;&#039;;&lt;br /&gt;
                el.style.filter = &#039;&#039;;&lt;br /&gt;
                cb();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(step);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;, initDocTabs);&lt;br /&gt;
} else {&lt;br /&gt;
    initDocTabs();&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        initDocTabs();&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Doc Section Switch — 좌측 섹션 전환&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
$(document).on(&#039;click&#039;, &#039;.doc-nav-item[data-section]&#039;, function () {&lt;br /&gt;
    var name = $(this).attr(&#039;data-section&#039;);&lt;br /&gt;
    var display = document.getElementById(&#039;doc-main-display&#039;);&lt;br /&gt;
    var titleEl = document.getElementById(&#039;doc-center-title&#039;);&lt;br /&gt;
    var tabBar = document.getElementById(&#039;doc-tab-bar-text&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (!display) return;&lt;br /&gt;
&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section]&#039;).removeClass(&#039;active&#039;);&lt;br /&gt;
    $(&#039;.doc-nav-item[data-section=&amp;quot;&#039; + name + &#039;&amp;quot;]&#039;).addClass(&#039;active&#039;);&lt;br /&gt;
&lt;br /&gt;
    if (name === &#039;text&#039;) {&lt;br /&gt;
        if (titleEl) titleEl.textContent = &#039;개요&#039;;&lt;br /&gt;
        if (tabBar) $(tabBar).show();&lt;br /&gt;
        var activeTab = tabBar ? tabBar.querySelector(&#039;.doc-tab.active&#039;) : null;&lt;br /&gt;
        if (!activeTab &amp;amp;&amp;amp; tabBar) activeTab = tabBar.querySelector(&#039;.doc-tab&#039;);&lt;br /&gt;
        if (activeTab) {&lt;br /&gt;
            var ref = activeTab.dataset.ref;&lt;br /&gt;
            var refEl = ref ? document.getElementById(ref) : null;&lt;br /&gt;
            display.innerHTML = refEl ? refEl.innerHTML : (activeTab.dataset.content || &#039;&#039;);&lt;br /&gt;
        }&lt;br /&gt;
    } else {&lt;br /&gt;
        if (titleEl) titleEl.textContent = name === &#039;factions&#039; ? &#039;세력&#039; : name === &#039;people&#039; ? &#039;인물&#039; : name;&lt;br /&gt;
        if (tabBar) $(tabBar).hide();&lt;br /&gt;
        var refEl = document.getElementById(&#039;doc-content-&#039; + name);&lt;br /&gt;
        display.innerHTML = refEl ? refEl.innerHTML : &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
});&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   CRT WebGL Renderer — cool-retro-term IBM DOS style&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function createNoiseTexture(gl) {&lt;br /&gt;
        var size = 512;&lt;br /&gt;
        var data = new Uint8Array(size * size * 4);&lt;br /&gt;
        var s = 12345;&lt;br /&gt;
        function rand() {&lt;br /&gt;
            s = (s * 1664525 + 1013904223) &amp;amp; 0xffffffff;&lt;br /&gt;
            return (s &amp;gt;&amp;gt;&amp;gt; 0) / 0xffffffff;&lt;br /&gt;
        }&lt;br /&gt;
        for (var i = 0; i &amp;lt; data.length; i++) {&lt;br /&gt;
            data[i] = (rand() * 255) | 0;&lt;br /&gt;
        }&lt;br /&gt;
        var tex = gl.createTexture();&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, tex);&lt;br /&gt;
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
        return tex;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var VERT = [&lt;br /&gt;
        &#039;attribute vec2 a_pos;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5);&#039;,&lt;br /&gt;
        &#039;  gl_Position = vec4(a_pos, 0.0, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    var FRAG = [&lt;br /&gt;
        &#039;precision mediump float;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_tex;&#039;,&lt;br /&gt;
        &#039;uniform sampler2D u_noise;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_res;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_imgSize;&#039;,&lt;br /&gt;
        &#039;uniform float u_time;&#039;,&lt;br /&gt;
        &#039;uniform vec2 u_noiseScale;&#039;,&lt;br /&gt;
        &#039;varying vec2 v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float sum2(vec2 v) { return v.x + v.y; }&#039;,&lt;br /&gt;
        &#039;float min2(vec2 v) { return min(v.x, v.y); }&#039;,&lt;br /&gt;
        &#039;float rgb2grey(vec3 v) { return dot(v, vec3(0.21, 0.72, 0.04)); }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 coverUV(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  float imgAR = u_imgSize.x / u_imgSize.y;&#039;,&lt;br /&gt;
        &#039;  float scrAR = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  float scale = imgAR / scrAR;&#039;,&lt;br /&gt;
        &#039;  float offsetY = (1.0 - scale) * 0.5;&#039;,&lt;br /&gt;
        &#039;  return vec2(uv.x, uv.y * scale + offsetY);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 barrel(vec2 v, vec2 cc, float k) {&#039;,&lt;br /&gt;
        &#039;  float ar = u_res.x / u_res.y;&#039;,&lt;br /&gt;
        &#039;  vec2 c2 = cc;&#039;,&lt;br /&gt;
        &#039;  if (ar &amp;gt; 1.0) c2.x /= ar; else c2.y *= ar;&#039;,&lt;br /&gt;
        &#039;  float dist = dot(c2, c2) * k;&#039;,&lt;br /&gt;
        &#039;  return v - cc * (1.0 + dist) * dist;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleInitialNoise(float t) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, vec2(fract(t/2048.0), fract(t/1048576.0)));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec4 sampleScreenNoise(vec2 uv) {&#039;,&lt;br /&gt;
        &#039;  return texture2D(u_noise, u_noiseScale * uv);&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyRgbShift(vec2 texUV, float shift) {&#039;,&lt;br /&gt;
        &#039;  vec2 d = vec2(shift, 0.0);&#039;,&lt;br /&gt;
        &#039;  vec3 r = texture2D(u_tex, clamp(texUV + d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 c = texture2D(u_tex, texUV).rgb;&#039;,&lt;br /&gt;
        &#039;  vec3 l = texture2D(u_tex, clamp(texUV - d, 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  return vec3(&#039;,&lt;br /&gt;
        &#039;    l.r*0.10 + r.r*0.30 + c.r*0.60,&#039;,&lt;br /&gt;
        &#039;    l.g*0.20 + r.g*0.20 + c.g*0.60,&#039;,&lt;br /&gt;
        &#039;    l.b*0.30 + r.b*0.10 + c.b*0.60&#039;,&lt;br /&gt;
        &#039;  );&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyBloom(vec2 texUV, float strength) {&#039;,&lt;br /&gt;
        &#039;  vec2 px = 2.0 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 acc = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  0.0), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0,  px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( 0.0, -px.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x,  px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2( px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  acc += texture2D(u_tex, clamp(texUV + vec2(-px.x, -px.y), 0.0, 1.0)).rgb * 0.5;&#039;,&lt;br /&gt;
        &#039;  return acc / 6.0 * strength;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec3 applyScanlines(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
        &#039;  float line = mod(uv.y * u_res.y, 2.0);&#039;,&lt;br /&gt;
        &#039;  vec3 hi = ((1.0 + 0.30) - 0.2 * col) * col;&#039;,&lt;br /&gt;
        &#039;  vec3 lo = ((1.0 - 0.30) + 0.1 * col) * col;&#039;,&lt;br /&gt;
        &#039;  return line &amp;lt; 1.0 ? lo : hi;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
&#039;vec3 applyRasterization(vec2 uv, vec3 col) {&#039;,&lt;br /&gt;
&#039;  float t = u_time;&#039;,&lt;br /&gt;
&#039;  vec2 noiseUV = uv + vec2(fract(t * 0.030), fract(t * 0.060));&#039;,&lt;br /&gt;
&#039;  float wobbleX = (texture2D(u_noise, noiseUV * 0.8).r - 0.5) * 0.0018;&#039;,&lt;br /&gt;
&#039;  float wobbleY = (texture2D(u_noise, noiseUV * 0.8 + 0.5).r - 0.5) * 0.0008;&#039;,&lt;br /&gt;
&#039;  vec2 wobbledUV = clamp(uv + vec2(wobbleX, wobbleY), 0.0, 1.0);&#039;,&lt;br /&gt;
&#039;  vec3 wobbled = texture2D(u_tex, wobbledUV).rgb;&#039;,&lt;br /&gt;
&#039;  return mix(col, wobbled, 0.35);&#039;,&lt;br /&gt;
&#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;float glowingLine(vec2 uv, float t) {&#039;,&lt;br /&gt;
&#039;  float pos = fract(t * 0.2);&#039;,&lt;br /&gt;
&#039;  float lineY = pos * (u_res.y + 330.0) - 120.0;&#039;,&lt;br /&gt;
        &#039;  float y = uv.y * u_res.y;&#039;,&lt;br /&gt;
        &#039;  return fract(smoothstep(-300.0, 0.0, y - lineY));&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;vec2 applyHSync(vec2 uv, vec4 noise, float strength) {&#039;,&lt;br /&gt;
        &#039;  float randval = strength - noise.r;&#039;,&lt;br /&gt;
        &#039;  float scale = step(0.0, randval) * randval * strength;&#039;,&lt;br /&gt;
        &#039;  float freq = mix(4.0, 40.0, noise.g);&#039;,&lt;br /&gt;
        &#039;  uv.x += sin((uv.y + u_time * 0.001) * freq) * scale;&#039;,&lt;br /&gt;
        &#039;  return uv;&#039;,&lt;br /&gt;
        &#039;}&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;void main() {&#039;,&lt;br /&gt;
        &#039;  vec2 cc = vec2(0.5) - v_uv;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float curvature = 0.18;&#039;,&lt;br /&gt;
        &#039;  vec2 uv = barrel(v_uv, cc, curvature);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float inScreen = min2(step(vec2(0.0), uv) - step(vec2(1.0), uv));&#039;,&lt;br /&gt;
        &#039;  if (inScreen &amp;lt; 0.5) { gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); return; }&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 texUV = clamp(coverUV(uv), 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec4 initNoise = sampleInitialNoise(u_time);&#039;,&lt;br /&gt;
        &#039;  vec4 screenNoise = sampleScreenNoise(uv);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV = applyHSync(texUV, initNoise, 0.006);&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  texUV += (vec2(screenNoise.b, screenNoise.a) - 0.5) * 0.0006;&#039;,&lt;br /&gt;
        &#039;  texUV = clamp(texUV, 0.0, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec3 col = applyRgbShift(texUV, 0.003);&#039;,&lt;br /&gt;
        &#039;  col += applyBloom(texUV, 0.22);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 bpx = 1.5 / u_res;&#039;,&lt;br /&gt;
        &#039;  vec3 blurCol = vec3(0.0);&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,   -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x, -bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  0.0  ), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2(-bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( 0.0,    bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  blurCol += texture2D(u_tex, clamp(texUV + vec2( bpx.x,  bpx.y), 0.0, 1.0)).rgb;&#039;,&lt;br /&gt;
        &#039;  col = mix(col, blurCol / 8.0, 0.40);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = applyScanlines(uv, col);&#039;,&lt;br /&gt;
        &#039;  col = applyRasterization(texUV, col);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float glow = glowingLine(uv, u_time);&#039;,&lt;br /&gt;
&#039;  col += glow * 0.08 * vec3(0.85, 0.95, 1.0);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float dist = length(cc);&#039;,&lt;br /&gt;
        &#039;  col += screenNoise.a * 0.07 * (1.0 - dist * 1.3);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  float grey = rgb2grey(col);&#039;,&lt;br /&gt;
        &#039;  vec3 phosphor = vec3(0.75, 0.88, 1.0);&#039;,&lt;br /&gt;
        &#039;  col = mix(col, grey * phosphor, 0.35);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  vec2 vig = v_uv * (1.0 - v_uv);&#039;,&lt;br /&gt;
        &#039;  col *= pow(vig.x * vig.y * 15.0, 0.25);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col *= 1.0 + (initNoise.g - 0.5) * 0.06;&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col += vec3(0.012) * (1.0 - dist) * (1.0 - dist);&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  col = pow(clamp(col, 0.0, 1.0), vec3(0.90));&#039;,&lt;br /&gt;
&lt;br /&gt;
        &#039;  gl_FragColor = vec4(col, 1.0);&#039;,&lt;br /&gt;
        &#039;}&#039;&lt;br /&gt;
    ].join(&#039;\n&#039;);&lt;br /&gt;
&lt;br /&gt;
    function initCRTCanvas(screen, imgEl) {&lt;br /&gt;
        var existing = screen.querySelector(&#039;.crt-webgl-canvas&#039;);&lt;br /&gt;
        if (existing) existing.remove();&lt;br /&gt;
&lt;br /&gt;
        var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.className = &#039;crt-webgl-canvas&#039;;&lt;br /&gt;
        canvas.style.cssText = &#039;position:absolute;inset:0;width:100%;height:100%;z-index:19;pointer-events:none;display:block;&#039;;&lt;br /&gt;
        screen.appendChild(canvas);&lt;br /&gt;
&lt;br /&gt;
        var gl = canvas.getContext(&#039;webgl&#039;) || canvas.getContext(&#039;experimental-webgl&#039;);&lt;br /&gt;
        if (!gl) return;&lt;br /&gt;
&lt;br /&gt;
        function compile(type, src) {&lt;br /&gt;
            var s = gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(s, src);&lt;br /&gt;
            gl.compileShader(s);&lt;br /&gt;
            if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {&lt;br /&gt;
                console.error(&#039;[CRT shader]&#039;, gl.getShaderInfoLog(s));&lt;br /&gt;
            }&lt;br /&gt;
            return s;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var prog = gl.createProgram();&lt;br /&gt;
        gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT));&lt;br /&gt;
        gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FRAG));&lt;br /&gt;
        gl.linkProgram(prog);&lt;br /&gt;
        gl.useProgram(prog);&lt;br /&gt;
&lt;br /&gt;
        var buf = gl.createBuffer();&lt;br /&gt;
        gl.bindBuffer(gl.ARRAY_BUFFER, buf);&lt;br /&gt;
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);&lt;br /&gt;
        var aPos = gl.getAttribLocation(prog, &#039;a_pos&#039;);&lt;br /&gt;
        gl.enableVertexAttribArray(aPos);&lt;br /&gt;
        gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);&lt;br /&gt;
&lt;br /&gt;
        var uTex     = gl.getUniformLocation(prog, &#039;u_tex&#039;);&lt;br /&gt;
        var uNoise   = gl.getUniformLocation(prog, &#039;u_noise&#039;);&lt;br /&gt;
        var uRes     = gl.getUniformLocation(prog, &#039;u_res&#039;);&lt;br /&gt;
        var uImgSize = gl.getUniformLocation(prog, &#039;u_imgSize&#039;);&lt;br /&gt;
        var uTime    = gl.getUniformLocation(prog, &#039;u_time&#039;);&lt;br /&gt;
        var uNoiseSc = gl.getUniformLocation(prog, &#039;u_noiseScale&#039;);&lt;br /&gt;
&lt;br /&gt;
        var imgTex = gl.createTexture();&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
        gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);&lt;br /&gt;
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);&lt;br /&gt;
&lt;br /&gt;
        gl.activeTexture(gl.TEXTURE1);&lt;br /&gt;
        createNoiseTexture(gl);&lt;br /&gt;
&lt;br /&gt;
        var texReady = false;&lt;br /&gt;
        function uploadImg() {&lt;br /&gt;
            if (!imgEl || !imgEl.complete || !imgEl.naturalWidth) return;&lt;br /&gt;
            try {&lt;br /&gt;
                gl.activeTexture(gl.TEXTURE0);&lt;br /&gt;
                gl.bindTexture(gl.TEXTURE_2D, imgTex);&lt;br /&gt;
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imgEl);&lt;br /&gt;
                texReady = true;&lt;br /&gt;
            } catch(e) { console.error(&#039;[CRT] tex:&#039;, e); }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var lastW = 0, lastH = 0;&lt;br /&gt;
        function resize() {&lt;br /&gt;
            var w = screen.offsetWidth, h = screen.offsetHeight;&lt;br /&gt;
            if (w === lastW &amp;amp;&amp;amp; h === lastH) return;&lt;br /&gt;
            lastW = w; lastH = h;&lt;br /&gt;
            canvas.width = w; canvas.height = h;&lt;br /&gt;
            gl.viewport(0, 0, w, h);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var raf;&lt;br /&gt;
        var t0 = performance.now();&lt;br /&gt;
&lt;br /&gt;
        function render() {&lt;br /&gt;
            raf = requestAnimationFrame(render);&lt;br /&gt;
            if (!texReady) { uploadImg(); return; }&lt;br /&gt;
            resize();&lt;br /&gt;
            var t = (performance.now() - t0) / 1000;&lt;br /&gt;
            gl.uniform1i(uTex, 0);&lt;br /&gt;
            gl.uniform1i(uNoise, 1);&lt;br /&gt;
            gl.uniform2f(uRes, canvas.width, canvas.height);&lt;br /&gt;
            gl.uniform2f(uImgSize, imgEl.naturalWidth, imgEl.naturalHeight);&lt;br /&gt;
            gl.uniform1f(uTime, t);&lt;br /&gt;
            gl.uniform2f(uNoiseSc, canvas.width / 512.0, canvas.height / 512.0);&lt;br /&gt;
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) { uploadImg(); }&lt;br /&gt;
        else { imgEl.addEventListener(&#039;load&#039;, uploadImg); }&lt;br /&gt;
&lt;br /&gt;
        render();&lt;br /&gt;
        screen._crtCleanup = function () { cancelAnimationFrame(raf); };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initAllCRTScreens(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        scope.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (screen) {&lt;br /&gt;
            if (screen.getAttribute(&#039;data-crt-webgl&#039;) === &#039;1&#039;) return;&lt;br /&gt;
            screen.setAttribute(&#039;data-crt-webgl&#039;, &#039;1&#039;);&lt;br /&gt;
            var frame = screen.closest(&#039;.crt-page-monitor-frame&#039;);&lt;br /&gt;
            if (!frame) return;&lt;br /&gt;
            var imgEl = frame.querySelector(&#039;.crt-page-monitor-slice-img, .crt-page-monitor-image-base img&#039;);&lt;br /&gt;
            if (imgEl &amp;amp;&amp;amp; imgEl.complete &amp;amp;&amp;amp; imgEl.naturalWidth) {&lt;br /&gt;
                initCRTCanvas(screen, imgEl);&lt;br /&gt;
            } else if (imgEl) {&lt;br /&gt;
                imgEl.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, imgEl); });&lt;br /&gt;
            } else {&lt;br /&gt;
                var obs = new MutationObserver(function () {&lt;br /&gt;
                    var img = frame.querySelector(&#039;.crt-page-monitor-slice-img&#039;);&lt;br /&gt;
                    if (!img) return;&lt;br /&gt;
                    obs.disconnect();&lt;br /&gt;
                    if (img.complete &amp;amp;&amp;amp; img.naturalWidth) {&lt;br /&gt;
                        initCRTCanvas(screen, img);&lt;br /&gt;
                    } else {&lt;br /&gt;
                        img.addEventListener(&#039;load&#039;, function () { initCRTCanvas(screen, img); });&lt;br /&gt;
                    }&lt;br /&gt;
                });&lt;br /&gt;
                obs.observe(frame, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $(function () { initAllCRTScreens(document); });&lt;br /&gt;
&lt;br /&gt;
    if (typeof mw !== &#039;undefined&#039; &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($c) {&lt;br /&gt;
            document.querySelectorAll(&#039;.crt-page-monitor-screen&#039;).forEach(function (s) {&lt;br /&gt;
                if (s._crtCleanup) s._crtCleanup();&lt;br /&gt;
                s.removeAttribute(&#039;data-crt-webgl&#039;);&lt;br /&gt;
            });&lt;br /&gt;
            initAllCRTScreens($c &amp;amp;&amp;amp; $c[0] ? $c[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
Progress System UI&lt;br /&gt;
MediaWiki:Common.js controlled frontend&lt;br /&gt;
========================================= */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (window.ProgressSystemWebUiInitialized) return;&lt;br /&gt;
    window.ProgressSystemWebUiInitialized = true;&lt;br /&gt;
&lt;br /&gt;
    var api = null;&lt;br /&gt;
&lt;br /&gt;
    function withApi(done, fail) {&lt;br /&gt;
        if (api) {&lt;br /&gt;
            done(api);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!mw.loader || typeof mw.loader.using !== &#039;function&#039;) {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        mw.loader.using([&#039;mediawiki.api&#039;]).then(function () {&lt;br /&gt;
            api = new mw.Api();&lt;br /&gt;
            done(api);&lt;br /&gt;
        }, function () {&lt;br /&gt;
            if (typeof fail === &#039;function&#039;) fail();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    var inFlightPageIds = new Set();&lt;br /&gt;
    var handledPageIds = new Set();&lt;br /&gt;
    var notificationQueue = [];&lt;br /&gt;
    var notificationActive = false;&lt;br /&gt;
    var summaryRequested = false;&lt;br /&gt;
    var currentSummary = null;&lt;br /&gt;
    var pendingSummary = null;&lt;br /&gt;
    var pendingOptions = null;&lt;br /&gt;
    var visibilityBound = false;&lt;br /&gt;
    var barTimerA = null;&lt;br /&gt;
    var barTimerB = null;&lt;br /&gt;
    var barTimerC = null;&lt;br /&gt;
    var summaryRetryTimer = null;&lt;br /&gt;
    var summaryRetryAttempts = 0;&lt;br /&gt;
&lt;br /&gt;
    function isLoggedIn() {&lt;br /&gt;
        return !!mw.config.get(&#039;wgUserName&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPageId() {&lt;br /&gt;
        var id = parseInt(mw.config.get(&#039;wgArticleId&#039;) || 0, 10);&lt;br /&gt;
        return Number.isFinite(id) ? id : 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isRewardableClientSide() {&lt;br /&gt;
        if (!isLoggedIn()) return false;&lt;br /&gt;
        if (parseInt(mw.config.get(&#039;wgNamespaceNumber&#039;), 10) !== 0) return false;&lt;br /&gt;
        if (mw.config.get(&#039;wgIsMainPage&#039;)) return false;&lt;br /&gt;
        if (getPageId() &amp;lt;= 0) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPanelHtml() {&lt;br /&gt;
        return &#039;&#039; +&lt;br /&gt;
            &#039;&amp;lt;div id=&amp;quot;progress-panel&amp;quot; class=&amp;quot;profile-progress-block is-syncing&amp;quot; aria-live=&amp;quot;polite&amp;quot; data-progress-state=&amp;quot;syncing&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-title-row&amp;quot; hidden&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-level-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-level-label&amp;quot;&amp;gt;SYNC&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-total-xp&amp;quot;&amp;gt;— XP&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-xp-bar&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-gain&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;div class=&amp;quot;progress-xp-fill&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-sub-row&amp;quot;&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-xp-next&amp;quot;&amp;gt;SYNCING&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                    &#039;&amp;lt;span class=&amp;quot;progress-daily-xp&amp;quot;&amp;gt;TODAY —&amp;lt;/span&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
                &#039;&amp;lt;div class=&amp;quot;progress-discovery-row&amp;quot;&amp;gt;DISCOVERED —&amp;lt;/div&amp;gt;&#039; +&lt;br /&gt;
            &#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDividerHtml() {&lt;br /&gt;
        /* 프로필 패널 최신 규칙: 레벨 패널과 버튼 영역 사이에 별도 나눔선은 만들지 않는다. */&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setPanelSync($panel) {&lt;br /&gt;
        if (!$panel || !$panel.length) return;&lt;br /&gt;
&lt;br /&gt;
        $panel.addClass(&#039;is-syncing&#039;).removeClass(&#039;is-max-level&#039;).attr(&#039;data-progress-state&#039;, &#039;syncing&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-title-row&#039;).text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text(&#039;SYNC&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(&#039;— XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(&#039;SYNCING&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED —&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-fill&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
        $panel.find(&#039;.progress-xp-gain&#039;).css({ transition: &#039;none&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function placePanel($panel) {&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $userBox = $right.children(&#039;.clbi-right-box&#039;).first();&lt;br /&gt;
        if (!$userBox.length) return false;&lt;br /&gt;
&lt;br /&gt;
        var $buttonArea = $userBox.children(&#039;.clbi-right-content&#039;).first();&lt;br /&gt;
        var $oldFallback = $panel.closest(&#039;.progress-panel-fallback&#039;);&lt;br /&gt;
&lt;br /&gt;
        if ($buttonArea.length) {&lt;br /&gt;
            var $divider = $(&#039;#profile-progress-divider&#039;);&lt;br /&gt;
&lt;br /&gt;
            $panel.insertBefore($buttonArea);&lt;br /&gt;
&lt;br /&gt;
            if (!$divider.length) {&lt;br /&gt;
                $divider = $(getDividerHtml());&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            $divider.insertAfter($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $(&#039;#profile-progress-divider&#039;).remove();&lt;br /&gt;
            $userBox.append($panel);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if ($oldFallback.length &amp;amp;&amp;amp; !$oldFallback.find(&#039;#progress-panel&#039;).length) {&lt;br /&gt;
            $oldFallback.remove();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensurePanel() {&lt;br /&gt;
        if (!isLoggedIn()) return $();&lt;br /&gt;
&lt;br /&gt;
        var $right = $(&#039;#clbi-right-sidebar&#039;);&lt;br /&gt;
        if (!$right.length) return $();&lt;br /&gt;
&lt;br /&gt;
        var $panel = $(&#039;#progress-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            $panel = $(getPanelHtml());&lt;br /&gt;
            if (!placePanel($panel)) return $();&lt;br /&gt;
            setPanelSync($panel);&lt;br /&gt;
        } else {&lt;br /&gt;
            $panel.addClass(&#039;profile-progress-block&#039;);&lt;br /&gt;
            placePanel($panel);&lt;br /&gt;
&lt;br /&gt;
            if (!currentSummary &amp;amp;&amp;amp; $panel.attr(&#039;data-progress-state&#039;) !== &#039;syncing&#039;) {&lt;br /&gt;
                setPanelSync($panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $(&#039;#progress-panel&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clampPercent(value) {&lt;br /&gt;
        return Math.max(0, Math.min(100, value || 0));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasXpNotification(items) {&lt;br /&gt;
        if (!items || !items.length) return false;&lt;br /&gt;
        return items.some(function (item) {&lt;br /&gt;
            return item &amp;amp;&amp;amp; item.type === &#039;xp&#039; &amp;amp;&amp;amp; parseInt(item.amount || 0, 10) &amp;gt; 0;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearBarTimers() {&lt;br /&gt;
        [barTimerA, barTimerB, barTimerC].forEach(function (timer) {&lt;br /&gt;
            if (timer) clearTimeout(timer);&lt;br /&gt;
        });&lt;br /&gt;
        barTimerA = null;&lt;br /&gt;
        barTimerB = null;&lt;br /&gt;
        barTimerC = null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setBarInstant($fill, $gain, percent) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
        percent = clampPercent(percent);&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: percent + &#039;%&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;none&#039;, left: &#039;0%&#039;, width: &#039;0%&#039;, opacity: 0 });&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
        $fill.css({ transition: &#039;&#039; });&lt;br /&gt;
        $gain.css({ transition: &#039;&#039; });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function animateGain($fill, $gain, fromPercent, toPercent, levelChanged) {&lt;br /&gt;
        clearBarTimers();&lt;br /&gt;
&lt;br /&gt;
        fromPercent = clampPercent(fromPercent);&lt;br /&gt;
        toPercent = clampPercent(toPercent);&lt;br /&gt;
&lt;br /&gt;
        $fill.css({ transition: &#039;none&#039;, width: fromPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
        if (levelChanged) {&lt;br /&gt;
            var firstDelta = Math.max(0, 100 - fromPercent);&lt;br /&gt;
&lt;br /&gt;
            $gain.css({&lt;br /&gt;
                transition: &#039;none&#039;,&lt;br /&gt;
                opacity: firstDelta &amp;gt; 0 ? 1 : 0,&lt;br /&gt;
                left: fromPercent + &#039;%&#039;,&lt;br /&gt;
                width: firstDelta + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
            barTimerA = setTimeout(function () {&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 540ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: &#039;100%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 260);&lt;br /&gt;
&lt;br /&gt;
            barTimerB = setTimeout(function () {&lt;br /&gt;
                $fill.css({ transition: &#039;none&#039;, width: &#039;0%&#039; });&lt;br /&gt;
                $gain.css({ transition: &#039;none&#039;, opacity: toPercent &amp;gt; 0 ? 1 : 0, left: &#039;0%&#039;, width: toPercent + &#039;%&#039; });&lt;br /&gt;
&lt;br /&gt;
                if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
                $fill.css({&lt;br /&gt;
                    transition: &#039;width 460ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                    width: toPercent + &#039;%&#039;&lt;br /&gt;
                });&lt;br /&gt;
            }, 860);&lt;br /&gt;
&lt;br /&gt;
            barTimerC = setTimeout(function () {&lt;br /&gt;
                $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
            }, 1380);&lt;br /&gt;
&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var delta = Math.max(0, toPercent - fromPercent);&lt;br /&gt;
&lt;br /&gt;
        if (delta &amp;lt;= 0.15) {&lt;br /&gt;
            setBarInstant($fill, $gain, toPercent);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $gain.css({&lt;br /&gt;
            transition: &#039;none&#039;,&lt;br /&gt;
            opacity: 1,&lt;br /&gt;
            left: fromPercent + &#039;%&#039;,&lt;br /&gt;
            width: delta + &#039;%&#039;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if ($fill[0]) $fill[0].offsetHeight;&lt;br /&gt;
&lt;br /&gt;
        barTimerA = setTimeout(function () {&lt;br /&gt;
            $fill.css({&lt;br /&gt;
                transition: &#039;width 560ms cubic-bezier(0.22, 0.7, 0.18, 1)&#039;,&lt;br /&gt;
                width: toPercent + &#039;%&#039;&lt;br /&gt;
            });&lt;br /&gt;
        }, 260);&lt;br /&gt;
&lt;br /&gt;
        barTimerB = setTimeout(function () {&lt;br /&gt;
            $gain.css({ transition: &#039;opacity 180ms ease&#039;, opacity: 0 });&lt;br /&gt;
        }, 940);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updatePanel(summary, options) {&lt;br /&gt;
        if (!summary) return;&lt;br /&gt;
&lt;br /&gt;
        options = options || {};&lt;br /&gt;
&lt;br /&gt;
        var $panel = ensurePanel();&lt;br /&gt;
        if (!$panel.length) {&lt;br /&gt;
            pendingSummary = $.extend({}, summary);&lt;br /&gt;
            pendingOptions = $.extend({}, options);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var level = summary.level || 1;&lt;br /&gt;
        var totalXp = summary.totalXp || 0;&lt;br /&gt;
        var xpIntoLevel = summary.xpIntoLevel || 0;&lt;br /&gt;
        var xpForNext = summary.xpForNextLevel || 1;&lt;br /&gt;
        var percent = clampPercent(summary.progressPercent);&lt;br /&gt;
        var isMaxLevel = !!summary.isMaxLevel;&lt;br /&gt;
        var dailyXp = summary.dailyXp || 0;&lt;br /&gt;
        var discoveries = summary.discoveryCount || 0;&lt;br /&gt;
        var title = summary.equippedTitle || summary.title || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        $panel.removeClass(&#039;is-syncing&#039;).toggleClass(&#039;is-max-level&#039;, isMaxLevel).attr(&#039;data-progress-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-level-label&#039;).text((isMaxLevel ? &#039;MAX &#039; : &#039;LVL &#039;) + level);&lt;br /&gt;
        $panel.find(&#039;.progress-total-xp&#039;).text(totalXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-xp-next&#039;).text(isMaxLevel ? &#039;MAX LEVEL&#039; : (xpIntoLevel + &#039; / &#039; + xpForNext + &#039; TO NEXT&#039;));&lt;br /&gt;
        $panel.find(&#039;.progress-daily-xp&#039;).text(&#039;TODAY &#039; + dailyXp + &#039; XP&#039;);&lt;br /&gt;
        $panel.find(&#039;.progress-discovery-row&#039;).text(&#039;DISCOVERED &#039; + discoveries);&lt;br /&gt;
&lt;br /&gt;
        var $title = $panel.find(&#039;.progress-title-row&#039;);&lt;br /&gt;
        if (title) {&lt;br /&gt;
            $title.text(title).prop(&#039;hidden&#039;, false);&lt;br /&gt;
        } else {&lt;br /&gt;
            $title.text(&#039;&#039;).prop(&#039;hidden&#039;, true);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $fill = $panel.find(&#039;.progress-xp-fill&#039;);&lt;br /&gt;
        var $gain = $panel.find(&#039;.progress-xp-gain&#039;);&lt;br /&gt;
        var animate = !!options.animateGain &amp;amp;&amp;amp; currentSummary &amp;amp;&amp;amp; totalXp &amp;gt; (currentSummary.totalXp || 0);&lt;br /&gt;
&lt;br /&gt;
        if (animate) {&lt;br /&gt;
            animateGain(&lt;br /&gt;
                $fill,&lt;br /&gt;
                $gain,&lt;br /&gt;
                clampPercent(currentSummary.progressPercent),&lt;br /&gt;
                percent,&lt;br /&gt;
                level !== (currentSummary.level || 1)&lt;br /&gt;
            );&lt;br /&gt;
        } else {&lt;br /&gt;
            setBarInstant($fill, $gain, percent);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        currentSummary = $.extend({}, summary);&lt;br /&gt;
        pendingSummary = null;&lt;br /&gt;
        pendingOptions = null;&lt;br /&gt;
        if (summaryRetryTimer) {&lt;br /&gt;
            clearTimeout(summaryRetryTimer);&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
        }&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearSummaryRetry() {&lt;br /&gt;
        if (summaryRetryTimer) clearTimeout(summaryRetryTimer);&lt;br /&gt;
        summaryRetryTimer = null;&lt;br /&gt;
        summaryRetryAttempts = 0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleSummaryRetry(delay) {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRetryTimer) return;&lt;br /&gt;
        if (summaryRetryAttempts &amp;gt;= 12) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRetryAttempts += 1;&lt;br /&gt;
        summaryRetryTimer = setTimeout(function () {&lt;br /&gt;
            summaryRetryTimer = null;&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, delay || 1800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSummary() {&lt;br /&gt;
        if (!isLoggedIn()) return;&lt;br /&gt;
        if (summaryRequested) return;&lt;br /&gt;
&lt;br /&gt;
        summaryRequested = true;&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.get({&lt;br /&gt;
                action: &#039;progress_summary&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_summary;&lt;br /&gt;
                if (payload &amp;amp;&amp;amp; payload.available &amp;amp;&amp;amp; payload.summary) {&lt;br /&gt;
                    clearSummaryRetry();&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: false });&lt;br /&gt;
                } else {&lt;br /&gt;
                    scheduleSummaryRetry(2200);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                scheduleSummaryRetry(2200);&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                summaryRequested = false;&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            summaryRequested = false;&lt;br /&gt;
            scheduleSummaryRetry(2200);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queueNotifications(items) {&lt;br /&gt;
        if (!items || !items.length) return;&lt;br /&gt;
&lt;br /&gt;
        items.forEach(function (item) {&lt;br /&gt;
            if (!item) return;&lt;br /&gt;
            notificationQueue.push(item);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        showNextNotification();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function notificationText(item) {&lt;br /&gt;
        if (item.type === &#039;xp&#039;) {&lt;br /&gt;
            return &#039;+&#039; + (item.amount || 0) + &#039; XP · &#039; + (item.label || &#039;문서 열람&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;achievement&#039;) {&lt;br /&gt;
            var xp = item.amount ? &#039; · +&#039; + item.amount + &#039; XP&#039; : &#039;&#039;;&lt;br /&gt;
            return &#039;업적 달성 · &#039; + (item.label || &#039;새 업적&#039;) + xp;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (item.type === &#039;level&#039;) {&lt;br /&gt;
            return item.label || &#039;레벨 상승&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return item.label || &#039;보상 획득&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function showNextNotification() {&lt;br /&gt;
        if (notificationActive) return;&lt;br /&gt;
        if (!notificationQueue.length) return;&lt;br /&gt;
&lt;br /&gt;
        notificationActive = true;&lt;br /&gt;
        var item = notificationQueue.shift();&lt;br /&gt;
        var $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!$root.length) {&lt;br /&gt;
            $(&#039;body&#039;).append(&#039;&amp;lt;div id=&amp;quot;progress-toast-root&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
            $root = $(&#039;#progress-toast-root&#039;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var $toast = $(&#039;&amp;lt;div class=&amp;quot;progress-toast&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
        $toast.text(notificationText(item));&lt;br /&gt;
        $root.append($toast);&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            $toast.addClass(&#039;is-visible&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            $toast.removeClass(&#039;is-visible&#039;);&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                $toast.remove();&lt;br /&gt;
                notificationActive = false;&lt;br /&gt;
                showNextNotification();&lt;br /&gt;
            }, 220);&lt;br /&gt;
        }, 2600);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPendingSummaryIfPossible() {&lt;br /&gt;
        if (!pendingSummary) return;&lt;br /&gt;
        updatePanel(pendingSummary, pendingOptions || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handlePageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (!isRewardableClientSide()) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        var pageId = getPageId();&lt;br /&gt;
        if (handledPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (inFlightPageIds.has(pageId)) {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        inFlightPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
        withApi(function (api) {&lt;br /&gt;
            api.postWithToken(&#039;csrf&#039;, {&lt;br /&gt;
                action: &#039;progress_view&#039;,&lt;br /&gt;
                format: &#039;json&#039;,&lt;br /&gt;
                formatversion: 2,&lt;br /&gt;
                errorformat: &#039;plaintext&#039;,&lt;br /&gt;
                pageid: pageId&lt;br /&gt;
            }).then(function (data) {&lt;br /&gt;
                var payload = data &amp;amp;&amp;amp; data.progress_view;&lt;br /&gt;
                if (!payload) return;&lt;br /&gt;
&lt;br /&gt;
                handledPageIds.add(pageId);&lt;br /&gt;
&lt;br /&gt;
                var animate = hasXpNotification(payload.notifications);&lt;br /&gt;
&lt;br /&gt;
                if (payload.summary) {&lt;br /&gt;
                    updatePanel(payload.summary, { animateGain: animate });&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (payload.notifications &amp;amp;&amp;amp; payload.notifications.length) {&lt;br /&gt;
                    queueNotifications(payload.notifications);&lt;br /&gt;
                }&lt;br /&gt;
            }).catch(function () {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }).always(function () {&lt;br /&gt;
                inFlightPageIds.delete(pageId);&lt;br /&gt;
            });&lt;br /&gt;
        }, function () {&lt;br /&gt;
            inFlightPageIds.delete(pageId);&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindVisibilitySync() {&lt;br /&gt;
        if (visibilityBound) return;&lt;br /&gt;
        visibilityBound = true;&lt;br /&gt;
&lt;br /&gt;
        document.addEventListener(&#039;visibilitychange&#039;, function () {&lt;br /&gt;
            if (document.visibilityState === &#039;visible&#039;) {&lt;br /&gt;
                requestSummary();&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bootProgressSystem(reason) {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        if (isRewardableClientSide()) {&lt;br /&gt;
            handlePageView();&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 350);&lt;br /&gt;
&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 1500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function handleSpaPageView() {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
&lt;br /&gt;
        requestAnimationFrame(function () {&lt;br /&gt;
            setTimeout(function () {&lt;br /&gt;
                handlePageView();&lt;br /&gt;
            }, 80);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applySummary(summary, options) {&lt;br /&gt;
        updatePanel(summary, options || { animateGain: false });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.ProgressSystemWebUi = {&lt;br /&gt;
        boot: bootProgressSystem,&lt;br /&gt;
        requestSummary: requestSummary,&lt;br /&gt;
        applySummary: applySummary,&lt;br /&gt;
        handlePageView: handlePageView,&lt;br /&gt;
        handleSpaPageView: handleSpaPageView,&lt;br /&gt;
        ensurePanel: ensurePanel&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        bindVisibilitySync();&lt;br /&gt;
        bootProgressSystem(&#039;documentReady&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
        ensurePanel();&lt;br /&gt;
        applyPendingSummaryIfPossible();&lt;br /&gt;
        setTimeout(function () {&lt;br /&gt;
            ensurePanel();&lt;br /&gt;
            applyPendingSummaryIfPossible();&lt;br /&gt;
            requestSummary();&lt;br /&gt;
        }, 120);&lt;br /&gt;
    });&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* CLBI Nations / Historical Events year tabs&lt;br /&gt;
 * Mirrors the country information panel model:&lt;br /&gt;
 * active tab uses .is-active/aria-selected and inactive pages use hidden.&lt;br /&gt;
 */&lt;br /&gt;
(function (mw, $) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    function activateClbiNationsHistoryYear(panel, targetYear) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var pages;&lt;br /&gt;
&lt;br /&gt;
        if (!panel || !targetYear) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        pages = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-page[data-year-panel]&#039;));&lt;br /&gt;
&lt;br /&gt;
        if (!tabs.length || !pages.length) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs.forEach(function (tab) {&lt;br /&gt;
            var active = tab.getAttribute(&#039;data-year&#039;) === targetYear;&lt;br /&gt;
            tab.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
            tab.setAttribute(&#039;aria-selected&#039;, active ? &#039;true&#039; : &#039;false&#039;);&lt;br /&gt;
            tab.setAttribute(&#039;tabindex&#039;, active ? &#039;0&#039; : &#039;-1&#039;);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        pages.forEach(function (page) {&lt;br /&gt;
            var active = page.getAttribute(&#039;data-year-panel&#039;) === targetYear;&lt;br /&gt;
            page.classList.toggle(&#039;is-active&#039;, active);&lt;br /&gt;
&lt;br /&gt;
            if (active) {&lt;br /&gt;
                page.removeAttribute(&#039;hidden&#039;);&lt;br /&gt;
            } else {&lt;br /&gt;
                page.setAttribute(&#039;hidden&#039;, &#039;hidden&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function moveClbiNationsHistoryYear(panel, direction) {&lt;br /&gt;
        var tabs;&lt;br /&gt;
        var activeIndex;&lt;br /&gt;
        var nextIndex;&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!panel) return false;&lt;br /&gt;
&lt;br /&gt;
        tabs = Array.prototype.slice.call(panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;));&lt;br /&gt;
        if (!tabs.length) return false;&lt;br /&gt;
&lt;br /&gt;
        activeIndex = tabs.findIndex(function (tab) {&lt;br /&gt;
            return tab.classList.contains(&#039;is-active&#039;) || tab.getAttribute(&#039;aria-selected&#039;) === &#039;true&#039;;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        if (activeIndex &amp;lt; 0) activeIndex = 0;&lt;br /&gt;
&lt;br /&gt;
        nextIndex = (activeIndex + direction + tabs.length) % tabs.length;&lt;br /&gt;
        target = tabs[nextIndex].getAttribute(&#039;data-year&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (activateClbiNationsHistoryYear(panel, target)) {&lt;br /&gt;
            tabs[nextIndex].focus();&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function initClbiNationsHistoryYearTabs(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var panels = scope.querySelectorAll(&#039;.clbi-nations-history-panel&#039;);&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(panels, function (panel) {&lt;br /&gt;
            if (panel.getAttribute(&#039;data-clbi-history-tabs-ready&#039;) === &#039;1&#039;) return;&lt;br /&gt;
&lt;br /&gt;
            panel.setAttribute(&#039;data-clbi-history-tabs-ready&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (activateClbiNationsHistoryYear(panel, tab.getAttribute(&#039;data-year&#039;))) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
            panel.addEventListener(&#039;keydown&#039;, function (event) {&lt;br /&gt;
                var tab = event.target.closest ? event.target.closest(&#039;.clbi-nations-history-year-button[data-year]&#039;) : null;&lt;br /&gt;
                var handled = false;&lt;br /&gt;
&lt;br /&gt;
                if (!tab || !panel.contains(tab)) return;&lt;br /&gt;
&lt;br /&gt;
                if (event.key === &#039;ArrowLeft&#039;) handled = moveClbiNationsHistoryYear(panel, -1);&lt;br /&gt;
                else if (event.key === &#039;ArrowRight&#039;) handled = moveClbiNationsHistoryYear(panel, 1);&lt;br /&gt;
                else if (event.key === &#039;Home&#039;) handled = activateClbiNationsHistoryYear(panel, (panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;) || {}).getAttribute &amp;amp;&amp;amp; panel.querySelector(&#039;.clbi-nations-history-year-button[data-year]&#039;).getAttribute(&#039;data-year&#039;));&lt;br /&gt;
                else if (event.key === &#039;End&#039;) {&lt;br /&gt;
                    var tabs = panel.querySelectorAll(&#039;.clbi-nations-history-year-button[data-year]&#039;);&lt;br /&gt;
                    var last = tabs[tabs.length - 1];&lt;br /&gt;
                    handled = last ? activateClbiNationsHistoryYear(panel, last.getAttribute(&#039;data-year&#039;)) : false;&lt;br /&gt;
                    if (handled) last.focus();&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (handled) {&lt;br /&gt;
                    event.preventDefault();&lt;br /&gt;
                    event.stopPropagation();&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.initClbiNationsHistoryYearTabs = initClbiNationsHistoryYearTabs;&lt;br /&gt;
&lt;br /&gt;
    $(function () {&lt;br /&gt;
        initClbiNationsHistoryYearTabs(document);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function ($content) {&lt;br /&gt;
            initClbiNationsHistoryYearTabs($content &amp;amp;&amp;amp; $content[0] ? $content[0] : document);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki, jQuery);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Decoration runtime renderer&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    var REGISTRY_TITLE = &#039;MediaWiki:Decorations.json&#039;;&lt;br /&gt;
    var RENDERED_ATTR = &#039;data-wiki-decoration-rendered&#039;;&lt;br /&gt;
    var HOST_ATTR = &#039;data-wiki-decoration-host&#039;;&lt;br /&gt;
    var MAIN_PAGE_PLACEMENT = &#039;main-body-well&#039;;&lt;br /&gt;
    var MAIN_PAGE_TARGET = &#039;.main-portal .main-body-well&#039;;&lt;br /&gt;
    var runtimeToken = 0;&lt;br /&gt;
    var lastRegistry = null;&lt;br /&gt;
    var lastRenderedPageKey = &#039;&#039;;&lt;br /&gt;
    var scheduledRender = 0;&lt;br /&gt;
    var nationsPlacementObserver = null;&lt;br /&gt;
    var observedNationsStack = null;&lt;br /&gt;
    var mainPagePlacementObserver = null;&lt;br /&gt;
    var mainPagePlacementMutationObserver = null;&lt;br /&gt;
    var observedMainPagePortal = null;&lt;br /&gt;
    var scheduledMainPageRebase = 0;&lt;br /&gt;
    var pixelAssetCache = {};&lt;br /&gt;
    var pixelCanvasCache = {};&lt;br /&gt;
&lt;br /&gt;
    function normalizePageName(value) {&lt;br /&gt;
        return String(value || &#039;&#039;)&lt;br /&gt;
            .split(&#039;?&#039;)[0]&lt;br /&gt;
            .replace(/^\/index\.php\//, &#039;&#039;)&lt;br /&gt;
            .replace(/_/g, &#039; &#039;)&lt;br /&gt;
            .trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function currentPageKey() {&lt;br /&gt;
        var raw = mw &amp;amp;&amp;amp; mw.config ? String(mw.config.get(&#039;wgPageName&#039;) || &#039;&#039;) : &#039;&#039;;&lt;br /&gt;
        return normalizePageName(raw) || raw || &#039;대문&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureMainPageBodyWell() {&lt;br /&gt;
        var portal;&lt;br /&gt;
        var topMount;&lt;br /&gt;
        var panel;&lt;br /&gt;
        var well;&lt;br /&gt;
        var manifesto;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return null;&lt;br /&gt;
&lt;br /&gt;
        portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        if (!portal) return null;&lt;br /&gt;
&lt;br /&gt;
        topMount = portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
        panel = portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        well = portal.querySelector(&#039;.main-body-well&#039;);&lt;br /&gt;
        manifesto = portal.querySelector(&#039;.main-manifesto&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!panel) {&lt;br /&gt;
            panel = document.createElement(&#039;div&#039;);&lt;br /&gt;
            panel.className = &#039;main-body-panel&#039;;&lt;br /&gt;
            panel.setAttribute(&#039;data-main-body-panel-generated&#039;, &#039;1&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (topMount &amp;amp;&amp;amp; topMount.parentNode) {&lt;br /&gt;
                if (topMount.nextSibling) {&lt;br /&gt;
                    topMount.parentNode.insertBefore(panel, topMount.nextSibling);&lt;br /&gt;
                } else {&lt;br /&gt;
                    topMount.parentNode.appendChild(panel);&lt;br /&gt;
                }&lt;br /&gt;
            } else {&lt;br /&gt;
                portal.appendChild(panel);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!well) {&lt;br /&gt;
            well = document.createElement(&#039;div&#039;);&lt;br /&gt;
            well.className = &#039;main-body-well&#039;;&lt;br /&gt;
            well.setAttribute(&#039;data-main-body-well-generated&#039;, &#039;1&#039;);&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        } else if (well.parentNode !== panel) {&lt;br /&gt;
            panel.appendChild(well);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (manifesto &amp;amp;&amp;amp; manifesto.parentNode !== well) {&lt;br /&gt;
            well.insertBefore(manifesto, well.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return well;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function cssAttrEscape(value) {&lt;br /&gt;
        return String(value || &#039;&#039;).replace(/\\/g, &#039;\\\\&#039;).replace(/&amp;quot;/g, &#039;\\&amp;quot;&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEra() {&lt;br /&gt;
        var content = document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        var title;&lt;br /&gt;
        var globe;&lt;br /&gt;
&lt;br /&gt;
        if (content) return content.getAttribute(&#039;data-era-content&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        title = document.querySelector(&#039;.clbi-nations-era-title-plate.is-active[data-era]&#039;);&lt;br /&gt;
        if (title) return title.getAttribute(&#039;data-era&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        globe = document.querySelector(&#039;.clbi-nations-globe-window[data-nations-globe]&#039;);&lt;br /&gt;
        if (globe) {&lt;br /&gt;
            return globe.getAttribute(&#039;data-current-era&#039;) || globe.getAttribute(&#039;data-nations-current-era&#039;) || globe.getAttribute(&#039;data-era-year&#039;) || &#039;&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsEraPanel() {&lt;br /&gt;
        var era = getActiveNationsEra();&lt;br /&gt;
        var selector;&lt;br /&gt;
        if (!era) return document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
        selector = &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;]&#039;;&lt;br /&gt;
        return document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden])&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getActiveNationsContinent() {&lt;br /&gt;
        var eraPanel = getActiveNationsEraPanel();&lt;br /&gt;
        var root = eraPanel || document;&lt;br /&gt;
        var tab = root.querySelector(&#039;.clbi-nations-tabpanel-tab.is-active[data-continent], .clbi-nations-tabpanel-tab[aria-selected=&amp;quot;true&amp;quot;][data-continent]&#039;);&lt;br /&gt;
        var panel;&lt;br /&gt;
&lt;br /&gt;
        if (tab) return tab.getAttribute(&#039;data-continent&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        panel = root.querySelector(&#039;.clbi-nations-tabpanel-continent.is-active[data-continent-panel]&#039;);&lt;br /&gt;
        if (panel) return panel.getAttribute(&#039;data-continent-panel&#039;) || &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        return &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationNationsBodySelector(era) {&lt;br /&gt;
        if (era) {&lt;br /&gt;
            return &#039;.clbi-nations-era-content[data-era-content=&amp;quot;&#039; + cssAttrEscape(era) + &#039;&amp;quot;] .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return &#039;.clbi-nations-era-content.is-active[data-era-content]:not([hidden]) .clbi-nations-tabpanel-body, .clbi-nations-tabpanel-body&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Decoration semantic placement resolver&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    장식 저장 데이터의 placement 값은 &amp;quot;사용자가 고른 의미상 위치&amp;quot;를 나타낸다.&lt;br /&gt;
    resolver는 그 의미값을 실제 DOM 부착 위치와 표시 조건으로 번역한다.&lt;br /&gt;
&lt;br /&gt;
    예: 시대 문서에서 사용자가 1950년 / 아메리카를 지정하면 의미상 scope는&lt;br /&gt;
    그 조합이지만, 이미지를 붙일 기준면은 대륙 패널 자체가 아니라&lt;br /&gt;
    .clbi-nations-tabpanel-body이다. 따라서 placement=nations-continent-body는&lt;br /&gt;
    .clbi-nations-tabpanel-body에 이미지를 붙이고, 현재 활성 연도와 대륙이 저장값과&lt;br /&gt;
    일치할 때만 렌더링한다.&lt;br /&gt;
&lt;br /&gt;
    유지보수 규칙:&lt;br /&gt;
    - 새 조합형 문서가 생기면 entry.target을 매번 특수하게 저장하지 말고 placement를 추가한다.&lt;br /&gt;
    - DevTools.js 에디터 미리보기와 Common.js 런타임 렌더러의 resolver는 같은 의미를 가져야 한다.&lt;br /&gt;
    - target은 물리적 기준면, era/continent 같은 필드는 표시 조건으로 다룬다.&lt;br /&gt;
    */&lt;br /&gt;
    function shouldUseNationsBodyPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var target = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (!document.querySelector(&#039;.clbi-nations-panel-stack&#039;)) return false;&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) return true;&lt;br /&gt;
        if (era || continent) return true;&lt;br /&gt;
        if (target.indexOf(&#039;clbi-nations-tabpanel-continent&#039;) !== -1) return true;&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function resolveDecorationPlacement(entry) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() || &#039;.liberty-content-main&#039;;&lt;br /&gt;
        var era = String(entry &amp;amp;&amp;amp; entry.era || &#039;&#039;).trim();&lt;br /&gt;
        var continent = String(entry &amp;amp;&amp;amp; entry.continent || &#039;&#039;).trim();&lt;br /&gt;
        var target;&lt;br /&gt;
&lt;br /&gt;
        if (!placement &amp;amp;&amp;amp; shouldUseNationsBodyPlacement(entry)) {&lt;br /&gt;
            placement = &#039;nations-continent-body&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;boot-gate&#039; || placement === &#039;loading-screen&#039;) {&lt;br /&gt;
            selector = &#039;#boot-gate-screen .boot-gate-decoration-layer, #boot-gate-screen&#039;;&lt;br /&gt;
            target = document.querySelector(selector);&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: !!document.getElementById(&#039;boot-gate-screen&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (placement === &#039;nations-continent-body&#039;) {&lt;br /&gt;
            selector = getDecorationNationsBodySelector(era);&lt;br /&gt;
            target = document.querySelector(selector) || document.querySelector(&#039;.clbi-nations-tabpanel-body&#039;) || document.querySelector(String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim());&lt;br /&gt;
            return {&lt;br /&gt;
                placement: placement,&lt;br /&gt;
                target: target,&lt;br /&gt;
                targetSelector: selector,&lt;br /&gt;
                visible: (!era || era === getActiveNationsEra()) &amp;amp;&amp;amp; (!continent || continent === getActiveNationsContinent())&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문의 일반 장식은 저장된 target과 관계없이 본문 우물 안에서 렌더링한다.&lt;br /&gt;
        우물이 overflow:hidden이므로 #1d1d1d 프레임 밖으로 나갈 수 없다.&lt;br /&gt;
        boot/loading 및 국가 패널 전용 placement는 위 분기에서 기존 동작을 유지한다.&lt;br /&gt;
        */&lt;br /&gt;
        var mainPageWell = ensureMainPageBodyWell();&lt;br /&gt;
        if (mainPageWell) {&lt;br /&gt;
            return {&lt;br /&gt;
                placement: MAIN_PAGE_PLACEMENT,&lt;br /&gt;
                target: mainPageWell,&lt;br /&gt;
                targetSelector: MAIN_PAGE_TARGET,&lt;br /&gt;
                visible: true&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            placement: placement,&lt;br /&gt;
            target: document.querySelector(selector),&lt;br /&gt;
            targetSelector: selector,&lt;br /&gt;
            visible: true&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeNumber(value, fallback) {&lt;br /&gt;
        var n = parseFloat(value);&lt;br /&gt;
        return Number.isFinite(n) ? n : fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeBool(value, fallback) {&lt;br /&gt;
        if (value === true || value === &#039;true&#039; || value === &#039;1&#039; || value === 1) return true;&lt;br /&gt;
        if (value === false || value === &#039;false&#039; || value === &#039;0&#039; || value === 0) return false;&lt;br /&gt;
        return fallback;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizeSrc(src) {&lt;br /&gt;
        src = String(src || &#039;&#039;).trim();&lt;br /&gt;
        if (!src) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(src) || src.charAt(0) === &#039;/&#039; || src.indexOf(&#039;data:&#039;) === 0 || src.indexOf(&#039;blob:&#039;) === 0) return src;&lt;br /&gt;
        return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(src);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function normalizeAssetType(entry) {&lt;br /&gt;
        var type = String(entry &amp;amp;&amp;amp; entry.assetType || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var ref = String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
&lt;br /&gt;
        if (type === &#039;clbi-pixel-json&#039; || type === &#039;pixel-rle&#039; || type === &#039;pixel-json&#039;) return &#039;pixel-json&#039;;&lt;br /&gt;
        if (!type &amp;amp;&amp;amp; /\.json(?:[?#].*)?$/i.test(ref)) return &#039;pixel-json&#039;;&lt;br /&gt;
        return type || &#039;image&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPixelJsonDecoration(entry) {&lt;br /&gt;
        return normalizeAssetType(entry) === &#039;pixel-json&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPixelJsonRef(entry) {&lt;br /&gt;
        return String(entry &amp;amp;&amp;amp; (entry.asset || entry.src) || &#039;&#039;).trim();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelJsonUrl(ref) {&lt;br /&gt;
        ref = String(ref || &#039;&#039;).trim();&lt;br /&gt;
        if (!ref) return &#039;&#039;;&lt;br /&gt;
        if (/^(?:https?:)?\/\//i.test(ref) || ref.charAt(0) === &#039;/&#039; || ref.indexOf(&#039;data:&#039;) === 0 || ref.indexOf(&#039;blob:&#039;) === 0) return ref;&lt;br /&gt;
        if (/^(?:file|파일):/i.test(ref)) return &#039;/index.php/Special:Redirect/file/&#039; + encodeURIComponent(ref.replace(/^(?:file|파일):/i, &#039;&#039;));&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
            if (ref.indexOf(&#039;:&#039;) !== -1) {&lt;br /&gt;
                return mw.util.getUrl(ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
            }&lt;br /&gt;
            return mw.util.getUrl(&#039;MediaWiki:&#039; + ref, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
        }&lt;br /&gt;
        return ref;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function parsePixelColor(value) {&lt;br /&gt;
        var text;&lt;br /&gt;
        var m;&lt;br /&gt;
        if (Array.isArray(value)) {&lt;br /&gt;
            return [&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[0]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[1]) || 0)),&lt;br /&gt;
                Math.max(0, Math.min(255, Number(value[2]) || 0)),&lt;br /&gt;
                value.length &amp;gt; 3 ? Math.max(0, Math.min(255, Number(value[3]) || 0)) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        m = text.match(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i);&lt;br /&gt;
        if (!m) return [0, 0, 0, 0];&lt;br /&gt;
        text = m[1];&lt;br /&gt;
        if (text.length === 3 || text.length === 4) {&lt;br /&gt;
            return [&lt;br /&gt;
                parseInt(text.charAt(0) + text.charAt(0), 16),&lt;br /&gt;
                parseInt(text.charAt(1) + text.charAt(1), 16),&lt;br /&gt;
                parseInt(text.charAt(2) + text.charAt(2), 16),&lt;br /&gt;
                text.length === 4 ? parseInt(text.charAt(3) + text.charAt(3), 16) : 255&lt;br /&gt;
            ];&lt;br /&gt;
        }&lt;br /&gt;
        return [&lt;br /&gt;
            parseInt(text.slice(0, 2), 16),&lt;br /&gt;
            parseInt(text.slice(2, 4), 16),&lt;br /&gt;
            parseInt(text.slice(4, 6), 16),&lt;br /&gt;
            text.length === 8 ? parseInt(text.slice(6, 8), 16) : 255&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    function decodePixelRle36(value) {&lt;br /&gt;
        var text = String(value || &#039;&#039;).trim();&lt;br /&gt;
        var parts;&lt;br /&gt;
        var runs = [];&lt;br /&gt;
        var i;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
&lt;br /&gt;
        if (!text) return runs;&lt;br /&gt;
        parts = text.split(&#039;,&#039;);&lt;br /&gt;
        for (i = 0; i + 3 &amp;lt; parts.length; i += 4) {&lt;br /&gt;
            x = parseInt(parts[i], 36);&lt;br /&gt;
            y = parseInt(parts[i + 1], 36);&lt;br /&gt;
            len = parseInt(parts[i + 2], 36);&lt;br /&gt;
            colorIndex = parseInt(parts[i + 3], 36);&lt;br /&gt;
            if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(len) || !Number.isFinite(colorIndex)) continue;&lt;br /&gt;
            runs.push([x, y, len, colorIndex]);&lt;br /&gt;
        }&lt;br /&gt;
        return runs;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function normalizePixelAsset(doc) {&lt;br /&gt;
        var encoding = String(doc &amp;amp;&amp;amp; (doc.encoding || doc.e) || &#039;&#039;).trim().toLowerCase();&lt;br /&gt;
        var width = Math.round(Number(doc &amp;amp;&amp;amp; (doc.width || doc.w)) || 0);&lt;br /&gt;
        var height = Math.round(Number(doc &amp;amp;&amp;amp; (doc.height || doc.h)) || 0);&lt;br /&gt;
        var paletteSource = Array.isArray(doc &amp;amp;&amp;amp; doc.palette) ? doc.palette : (Array.isArray(doc &amp;amp;&amp;amp; doc.p) ? doc.p : []);&lt;br /&gt;
        var palette = paletteSource.map(parsePixelColor);&lt;br /&gt;
        var runs;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        CLBI Pixel Forge v0.2.3 compact format:&lt;br /&gt;
        - MediaWiki 단일 문서 크기 제한을 피하기 위해 사람이 읽기 쉬운 [[x,y,len,c], ...] 배열 대신&lt;br /&gt;
          base36 토큰 문자열을 쓴다.&lt;br /&gt;
        - 형식은 x,y,len,colorIndex를 4개 토큰 단위로 반복한 rle36이다.&lt;br /&gt;
        - 복원 후 좌표계는 기존 runs 배열과 완전히 같으며, 최종 CSS px에 1:1로 찍는다.&lt;br /&gt;
        */&lt;br /&gt;
        if ((encoding === &#039;rle36&#039; || encoding === &#039;clbi-rle36&#039;) &amp;amp;&amp;amp; typeof (doc &amp;amp;&amp;amp; doc.r) === &#039;string&#039;) {&lt;br /&gt;
            runs = decodePixelRle36(doc.r);&lt;br /&gt;
        } else {&lt;br /&gt;
            runs = Array.isArray(doc &amp;amp;&amp;amp; doc.runs) ? doc.runs : [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (!width || !height || width &amp;lt; 1 || height &amp;lt; 1) throw new Error(&#039;invalid pixel decoration size&#039;);&lt;br /&gt;
        if (width &amp;gt; 8192 || height &amp;gt; 8192) throw new Error(&#039;pixel decoration too large&#039;);&lt;br /&gt;
        return {&lt;br /&gt;
            type: String(doc &amp;amp;&amp;amp; (doc.type || doc.t) || &#039;clbi-pixel-decoration&#039;),&lt;br /&gt;
            version: Number(doc &amp;amp;&amp;amp; (doc.version || doc.v)) || 1,&lt;br /&gt;
            encoding: encoding || &#039;runs&#039;,&lt;br /&gt;
            width: width,&lt;br /&gt;
            height: height,&lt;br /&gt;
            palette: palette,&lt;br /&gt;
            runs: runs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchPixelAsset(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelAssetCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {&lt;br /&gt;
            promise: fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
                .then(function (response) {&lt;br /&gt;
                    if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                    return response.json();&lt;br /&gt;
                })&lt;br /&gt;
                .then(normalizePixelAsset)&lt;br /&gt;
        };&lt;br /&gt;
        pixelAssetCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawPixelAssetToCanvas(canvas, asset) {&lt;br /&gt;
        var ctx;&lt;br /&gt;
        var image;&lt;br /&gt;
        var data;&lt;br /&gt;
        var i;&lt;br /&gt;
        var run;&lt;br /&gt;
        var x;&lt;br /&gt;
        var y;&lt;br /&gt;
        var len;&lt;br /&gt;
        var color;&lt;br /&gt;
        var colorIndex;&lt;br /&gt;
        var p;&lt;br /&gt;
        var k;&lt;br /&gt;
&lt;br /&gt;
        canvas.width = asset.width;&lt;br /&gt;
        canvas.height = asset.height;&lt;br /&gt;
        ctx = canvas.getContext(&#039;2d&#039;);&lt;br /&gt;
        ctx.imageSmoothingEnabled = false;&lt;br /&gt;
        image = ctx.createImageData(asset.width, asset.height);&lt;br /&gt;
        data = image.data;&lt;br /&gt;
&lt;br /&gt;
        for (i = 0; i &amp;lt; asset.runs.length; i += 1) {&lt;br /&gt;
            run = asset.runs[i];&lt;br /&gt;
            if (!Array.isArray(run) || run.length &amp;lt; 4) continue;&lt;br /&gt;
            x = Math.round(Number(run[0]) || 0);&lt;br /&gt;
            y = Math.round(Number(run[1]) || 0);&lt;br /&gt;
            len = Math.round(Number(run[2]) || 0);&lt;br /&gt;
            colorIndex = Math.round(Number(run[3]) || 0);&lt;br /&gt;
            color = asset.palette[colorIndex];&lt;br /&gt;
            if (!color || len &amp;lt;= 0 || y &amp;lt; 0 || y &amp;gt;= asset.height || x &amp;gt;= asset.width) continue;&lt;br /&gt;
            if (x &amp;lt; 0) {&lt;br /&gt;
                len += x;&lt;br /&gt;
                x = 0;&lt;br /&gt;
            }&lt;br /&gt;
            len = Math.min(len, asset.width - x);&lt;br /&gt;
            for (k = 0; k &amp;lt; len; k += 1) {&lt;br /&gt;
                p = ((y * asset.width) + x + k) * 4;&lt;br /&gt;
                data[p] = color[0];&lt;br /&gt;
                data[p + 1] = color[1];&lt;br /&gt;
                data[p + 2] = color[2];&lt;br /&gt;
                data[p + 3] = color[3];&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        ctx.putImageData(image, 0, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preparePixelCanvas(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached;&lt;br /&gt;
        if (!url) return Promise.reject(new Error(&#039;pixel json ref is empty&#039;));&lt;br /&gt;
        cached = pixelCanvasCache[url];&lt;br /&gt;
        if (cached) return cached.promise;&lt;br /&gt;
        cached = {};&lt;br /&gt;
        cached.promise = fetchPixelAsset(ref).then(function (asset) {&lt;br /&gt;
            var canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            drawPixelAssetToCanvas(canvas, asset);&lt;br /&gt;
            canvas.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;data-decoration-asset-size&#039;, asset.width + &#039;x&#039; + asset.height);&lt;br /&gt;
            cached.canvas = canvas;&lt;br /&gt;
            cached.width = asset.width;&lt;br /&gt;
            cached.height = asset.height;&lt;br /&gt;
            return canvas;&lt;br /&gt;
        });&lt;br /&gt;
        pixelCanvasCache[url] = cached;&lt;br /&gt;
        return cached.promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedPixelCanvasSync(ref) {&lt;br /&gt;
        var url = normalizePixelJsonUrl(ref);&lt;br /&gt;
        var cached = url ? pixelCanvasCache[url] : null;&lt;br /&gt;
        return cached &amp;amp;&amp;amp; cached.canvas ? cached.canvas : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function queryDecorationTarget(selector) {&lt;br /&gt;
        selector = String(selector || &#039;&#039;).trim();&lt;br /&gt;
        if (!selector) return null;&lt;br /&gt;
        try {&lt;br /&gt;
            return document.querySelector(selector);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function usesCanonicalMainPageCoordinates(entry, actualTarget) {&lt;br /&gt;
        var placement = String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim();&lt;br /&gt;
        var selector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim();&lt;br /&gt;
        var declaredTarget;&lt;br /&gt;
&lt;br /&gt;
        if (placement === MAIN_PAGE_PLACEMENT) return true;&lt;br /&gt;
        if (!actualTarget || !selector) return false;&lt;br /&gt;
&lt;br /&gt;
        declaredTarget = queryDecorationTarget(selector);&lt;br /&gt;
        return declaredTarget === actualTarget;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getDecorationLocalPosition(entry, actualTarget) {&lt;br /&gt;
        var x = normalizeNumber(entry &amp;amp;&amp;amp; entry.x, 0);&lt;br /&gt;
        var y = normalizeNumber(entry &amp;amp;&amp;amp; entry.y, 0);&lt;br /&gt;
        var sourceX = x;&lt;br /&gt;
        var sourceY = y;&lt;br /&gt;
        var sourceSelector;&lt;br /&gt;
        var sourceTarget;&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var actualRect;&lt;br /&gt;
        var sourceOriginX;&lt;br /&gt;
        var sourceOriginY;&lt;br /&gt;
        var actualOriginX;&lt;br /&gt;
        var actualOriginY;&lt;br /&gt;
&lt;br /&gt;
        /*&lt;br /&gt;
        대문 데코 좌표 보존&lt;br /&gt;
        -----------------------------------------&lt;br /&gt;
        저장된 x/y는 기존 entry.target 좌표계를 기준으로 만들어졌다.&lt;br /&gt;
        데코를 main-body-well 안으로 옮길 때 x/y를 그대로 적용하면&lt;br /&gt;
        더 아래에서 시작하는 새 좌표계 때문에 장식이 바닥으로 밀린다.&lt;br /&gt;
&lt;br /&gt;
        실제 화면상의 위치는 유지하고 부모만 우물로 바꾼다.&lt;br /&gt;
        기존 기준면의 padding-box 원점과 새 우물의 padding-box 원점 차이를&lt;br /&gt;
        정수 px로 더해 새 로컬 좌표를 만든다.&lt;br /&gt;
        */&lt;br /&gt;
        if (&lt;br /&gt;
            currentPageKey() === &#039;대문&#039; &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList &amp;amp;&amp;amp;&lt;br /&gt;
            actualTarget.classList.contains(&#039;main-body-well&#039;) &amp;amp;&amp;amp;&lt;br /&gt;
            !usesCanonicalMainPageCoordinates(entry, actualTarget)&lt;br /&gt;
        ) {&lt;br /&gt;
            sourceSelector = String(entry &amp;amp;&amp;amp; entry.target || &#039;&#039;).trim() ||&lt;br /&gt;
                &#039;.liberty-content-main&#039;;&lt;br /&gt;
            sourceTarget = queryDecorationTarget(sourceSelector) ||&lt;br /&gt;
                document.querySelector(&#039;.liberty-content-main&#039;) ||&lt;br /&gt;
                document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (sourceTarget &amp;amp;&amp;amp; sourceTarget !== actualTarget) {&lt;br /&gt;
                sourceRect = sourceTarget.getBoundingClientRect();&lt;br /&gt;
                actualRect = actualTarget.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
                sourceOriginX = sourceRect.left + normalizeNumber(sourceTarget.clientLeft, 0);&lt;br /&gt;
                sourceOriginY = sourceRect.top + normalizeNumber(sourceTarget.clientTop, 0);&lt;br /&gt;
                actualOriginX = actualRect.left + normalizeNumber(actualTarget.clientLeft, 0);&lt;br /&gt;
                actualOriginY = actualRect.top + normalizeNumber(actualTarget.clientTop, 0);&lt;br /&gt;
&lt;br /&gt;
                x += Math.round(sourceOriginX - actualOriginX);&lt;br /&gt;
                y += Math.round(sourceOriginY - actualOriginY);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            x: Math.round(x),&lt;br /&gt;
            y: Math.round(y),&lt;br /&gt;
            sourceX: Math.round(sourceX),&lt;br /&gt;
            sourceY: Math.round(sourceY),&lt;br /&gt;
            sourceSelector: sourceSelector || &#039;&#039;&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecorationBaseStyle(node, entry, actualTarget) {&lt;br /&gt;
        var position = getDecorationLocalPosition(entry, actualTarget);&lt;br /&gt;
&lt;br /&gt;
        node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
        node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
        node.style.opacity = String(normalizeNumber(entry.opacity, 1));&lt;br /&gt;
        node.style.zIndex = String(Math.round(normalizeNumber(entry.zIndex, 0)));&lt;br /&gt;
        node.style.pointerEvents = String(entry.pointerEvents || &#039;none&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (position.sourceSelector) {&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-coordinate-source&#039;, position.sourceSelector);&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-x&#039;, String(position.sourceX));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-source-y&#039;, String(position.sourceY));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (entry.blendMode) node.style.mixBlendMode = String(entry.blendMode);&lt;br /&gt;
        if (entry.filter) node.style.filter = String(entry.filter);&lt;br /&gt;
        if (entry.transform) node.style.transform = String(entry.transform);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isDecorationNodeActiveForNationsState(node) {&lt;br /&gt;
        var era = node ? String(node.getAttribute(&#039;data-decoration-era&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        var continent = node ? String(node.getAttribute(&#039;data-decoration-continent&#039;) || &#039;&#039;).trim() : &#039;&#039;;&lt;br /&gt;
        if (era &amp;amp;&amp;amp; era !== getActiveNationsEra()) return false;&lt;br /&gt;
        if (continent &amp;amp;&amp;amp; continent !== getActiveNationsContinent()) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDecorationNodeVisibility(node, visible) {&lt;br /&gt;
        if (!node) return;&lt;br /&gt;
        visible = visible !== false;&lt;br /&gt;
        /* aria-hidden stays true because wiki decorations are purely visual. */&lt;br /&gt;
        if (node.hidden !== !visible) node.hidden = !visible;&lt;br /&gt;
        if (node.style.display !== (visible ? &#039;&#039; : &#039;none&#039;)) node.style.display = visible ? &#039;&#039; : &#039;none&#039;;&lt;br /&gt;
        node.setAttribute(&#039;data-decoration-visible&#039;, visible ? &#039;1&#039; : &#039;0&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateDecorationVisibility(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var count = 0;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            setDecorationNodeVisibility(node, isDecorationNodeActiveForNationsState(node));&lt;br /&gt;
            count += 1;&lt;br /&gt;
        });&lt;br /&gt;
        return count;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function hasRenderedDecorations(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelector ? root : document;&lt;br /&gt;
        return !!(scope &amp;amp;&amp;amp; scope.querySelector &amp;amp;&amp;amp; scope.querySelector(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyPixelJsonDecoration(entry, target, visible) {&lt;br /&gt;
        var ref = getPixelJsonRef(entry);&lt;br /&gt;
        var template;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var ctx;&lt;br /&gt;
        if (!ref) return false;&lt;br /&gt;
&lt;br /&gt;
        function makeCanvasFromTemplate(source) {&lt;br /&gt;
            var out = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            var outCtx;&lt;br /&gt;
            out.className = &#039;wiki-decoration wiki-decoration-pixel-json&#039;;&lt;br /&gt;
            out.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
            out.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-type&#039;, &#039;pixel-json&#039;);&lt;br /&gt;
            if (entry.placement) out.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
            if (entry.era) out.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
            if (entry.continent) out.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
            out.width = source.width;&lt;br /&gt;
            out.height = source.height;&lt;br /&gt;
            out.style.imageRendering = &#039;pixelated&#039;;&lt;br /&gt;
            out.style.width = source.width + &#039;px&#039;;&lt;br /&gt;
            out.style.height = source.height + &#039;px&#039;;&lt;br /&gt;
            out.setAttribute(&#039;data-decoration-asset-size&#039;, source.width + &#039;x&#039; + source.height);&lt;br /&gt;
            outCtx = out.getContext(&#039;2d&#039;);&lt;br /&gt;
            outCtx.imageSmoothingEnabled = false;&lt;br /&gt;
            outCtx.drawImage(source, 0, 0);&lt;br /&gt;
            applyDecorationBaseStyle(out, entry, target);&lt;br /&gt;
            setDecorationNodeVisibility(out, visible);&lt;br /&gt;
            return out;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        template = getPreparedPixelCanvasSync(ref);&lt;br /&gt;
        if (template) {&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(template));&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        /* Fallback path only.  A full entry pack should prepare the template before the&lt;br /&gt;
           normal UI is released, so users should not see a blank decoration canvas. */&lt;br /&gt;
        preparePixelCanvas(ref).then(function (source) {&lt;br /&gt;
            if (!target || !target.parentNode || !source) return;&lt;br /&gt;
            target.appendChild(makeCanvasFromTemplate(source));&lt;br /&gt;
        }).catch(function () {});&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function preloadPixelAssetsForRegistry(registry) {&lt;br /&gt;
        var promises = [];&lt;br /&gt;
        decorationList(registry).forEach(function (entry) {&lt;br /&gt;
            var ref;&lt;br /&gt;
            if (!entry || !matchesPage(entry) || !isPixelJsonDecoration(entry)) return;&lt;br /&gt;
            ref = getPixelJsonRef(entry);&lt;br /&gt;
            if (!ref) return;&lt;br /&gt;
            promises.push(preparePixelCanvas(ref).catch(function () { return null; }));&lt;br /&gt;
        });&lt;br /&gt;
        return Promise.all(promises);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decorationList(registry) {&lt;br /&gt;
        if (!registry || typeof registry !== &#039;object&#039;) return [];&lt;br /&gt;
        if (Array.isArray(registry)) return registry;&lt;br /&gt;
        if (Array.isArray(registry.decorations)) return registry.decorations;&lt;br /&gt;
        return [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function matchesPage(entry) {&lt;br /&gt;
        var page = currentPageKey();&lt;br /&gt;
        var underscored = page.replace(/ /g, &#039;_&#039;);&lt;br /&gt;
        var pages = entry &amp;amp;&amp;amp; entry.pages;&lt;br /&gt;
        var target = entry &amp;amp;&amp;amp; entry.page;&lt;br /&gt;
        var i;&lt;br /&gt;
&lt;br /&gt;
        if (normalizeBool(entry &amp;amp;&amp;amp; entry.global, false)) return true;&lt;br /&gt;
        if (String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;boot-gate&#039; || String(entry &amp;amp;&amp;amp; entry.placement || &#039;&#039;).trim() === &#039;loading-screen&#039;) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (normalizePageName(target).toLowerCase() === &#039;__boot__&#039; || normalizePageName(target).toLowerCase() === &#039;loading-screen&#039;) return true;&lt;br /&gt;
        if (!target &amp;amp;&amp;amp; !pages) return true;&lt;br /&gt;
&lt;br /&gt;
        if (Array.isArray(pages)) {&lt;br /&gt;
            for (i = 0; i &amp;lt; pages.length; i += 1) {&lt;br /&gt;
                if (normalizePageName(pages[i]) === page || String(pages[i] || &#039;&#039;) === underscored) return true;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        target = normalizePageName(target);&lt;br /&gt;
        return target === page || target === underscored;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function clearRendered(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        Array.prototype.forEach.call(scope.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;), function (node) {&lt;br /&gt;
            if (node.parentNode) node.parentNode.removeChild(node);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureHost(target) {&lt;br /&gt;
        var style;&lt;br /&gt;
        if (!target) return;&lt;br /&gt;
        target.setAttribute(HOST_ATTR, &#039;1&#039;);&lt;br /&gt;
        style = window.getComputedStyle ? window.getComputedStyle(target) : null;&lt;br /&gt;
        if (style &amp;amp;&amp;amp; style.position === &#039;static&#039;) target.style.position = &#039;relative&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function applyDecoration(entry) {&lt;br /&gt;
        var resolved;&lt;br /&gt;
        var target;&lt;br /&gt;
        var src;&lt;br /&gt;
        var img;&lt;br /&gt;
        var width;&lt;br /&gt;
        var height;&lt;br /&gt;
&lt;br /&gt;
        if (!entry || typeof entry !== &#039;object&#039; || normalizeBool(entry.enabled, true) === false) return false;&lt;br /&gt;
        resolved = resolveDecorationPlacement(entry);&lt;br /&gt;
        if (!resolved) return false;&lt;br /&gt;
        target = resolved.target;&lt;br /&gt;
        if (!target) return false;&lt;br /&gt;
&lt;br /&gt;
        ensureHost(target);&lt;br /&gt;
&lt;br /&gt;
        if (isPixelJsonDecoration(entry)) {&lt;br /&gt;
            return applyPixelJsonDecoration(entry, target, resolved.visible !== false);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        src = normalizeSrc(entry.src);&lt;br /&gt;
        if (!src) return false;&lt;br /&gt;
&lt;br /&gt;
        img = document.createElement(&#039;img&#039;);&lt;br /&gt;
        img.className = &#039;wiki-decoration&#039;;&lt;br /&gt;
        img.setAttribute(RENDERED_ATTR, &#039;1&#039;);&lt;br /&gt;
        img.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        img.setAttribute(&#039;alt&#039;, &#039;&#039;);&lt;br /&gt;
        img.setAttribute(&#039;decoding&#039;, &#039;async&#039;);&lt;br /&gt;
        img.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        img.setAttribute(&#039;data-decoration-id&#039;, String(entry.id || &#039;&#039;));&lt;br /&gt;
        if (entry.placement) img.setAttribute(&#039;data-decoration-placement&#039;, String(entry.placement));&lt;br /&gt;
        if (entry.era) img.setAttribute(&#039;data-decoration-era&#039;, String(entry.era));&lt;br /&gt;
        if (entry.continent) img.setAttribute(&#039;data-decoration-continent&#039;, String(entry.continent));&lt;br /&gt;
        img.src = src;&lt;br /&gt;
&lt;br /&gt;
        applyDecorationBaseStyle(img, entry, target);&lt;br /&gt;
        width = normalizeNumber(entry.width, NaN);&lt;br /&gt;
        height = normalizeNumber(entry.height, NaN);&lt;br /&gt;
        if (Number.isFinite(width) &amp;amp;&amp;amp; width &amp;gt; 0) img.style.width = width + &#039;px&#039;;&lt;br /&gt;
        if (Number.isFinite(height) &amp;amp;&amp;amp; height &amp;gt; 0) img.style.height = height + &#039;px&#039;;&lt;br /&gt;
        if (entry.objectFit) img.style.objectFit = String(entry.objectFit);&lt;br /&gt;
        setDecorationNodeVisibility(img, resolved.visible !== false);&lt;br /&gt;
&lt;br /&gt;
        target.appendChild(img);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mainPageLayoutSignature() {&lt;br /&gt;
        var source = document.querySelector(&#039;.liberty-content-main&#039;);&lt;br /&gt;
        var well = ensureMainPageBodyWell();&lt;br /&gt;
        var sourceRect;&lt;br /&gt;
        var wellRect;&lt;br /&gt;
&lt;br /&gt;
        if (!source || !well) return &#039;&#039;;&lt;br /&gt;
        sourceRect = source.getBoundingClientRect();&lt;br /&gt;
        wellRect = well.getBoundingClientRect();&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            Math.round((wellRect.left + normalizeNumber(well.clientLeft, 0)) - (sourceRect.left + normalizeNumber(source.clientLeft, 0))),&lt;br /&gt;
            Math.round((wellRect.top + normalizeNumber(well.clientTop, 0)) - (sourceRect.top + normalizeNumber(source.clientTop, 0))),&lt;br /&gt;
            Math.round(wellRect.width),&lt;br /&gt;
            Math.round(wellRect.height)&lt;br /&gt;
        ].join(&#039;|&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isMainPageDecorationLayoutReady() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var panel = portal &amp;amp;&amp;amp; portal.querySelector(&#039;.main-body-panel&#039;);&lt;br /&gt;
        var topMount = portal &amp;amp;&amp;amp; portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!portal || !panel || !ensureMainPageBodyWell()) return false;&lt;br /&gt;
        if (!document.body || !document.body.classList.contains(&#039;clbi-main-page&#039;)) return false;&lt;br /&gt;
        if (topMount &amp;amp;&amp;amp; !topMount.querySelector(&#039;.portal-category-nav&#039;)) return false;&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function waitForMainPageDecorationLayout() {&lt;br /&gt;
        var started;&lt;br /&gt;
        var previous = &#039;&#039;;&lt;br /&gt;
        var stableFrames = 0;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return Promise.resolve(true);&lt;br /&gt;
        started = Date.now();&lt;br /&gt;
&lt;br /&gt;
        return new Promise(function (resolve) {&lt;br /&gt;
            function check() {&lt;br /&gt;
                var signature = mainPageLayoutSignature();&lt;br /&gt;
&lt;br /&gt;
                if (signature &amp;amp;&amp;amp; signature === previous) stableFrames += 1;&lt;br /&gt;
                else stableFrames = 0;&lt;br /&gt;
                previous = signature;&lt;br /&gt;
&lt;br /&gt;
                if ((isMainPageDecorationLayoutReady() &amp;amp;&amp;amp; stableFrames &amp;gt;= 2) || Date.now() - started &amp;gt;= 1200) {&lt;br /&gt;
                    resolve(true);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                if (window.requestAnimationFrame) window.requestAnimationFrame(check);&lt;br /&gt;
                else window.setTimeout(check, 16);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function rebaseLegacyMainPageDecorations() {&lt;br /&gt;
        var well;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) return;&lt;br /&gt;
        well = ensureMainPageBodyWell();&lt;br /&gt;
        if (!well) return;&lt;br /&gt;
&lt;br /&gt;
        document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-coordinate-source]&#039;).forEach(function (node) {&lt;br /&gt;
            var position = getDecorationLocalPosition({&lt;br /&gt;
                target: node.getAttribute(&#039;data-decoration-coordinate-source&#039;) || &#039;.liberty-content-main&#039;,&lt;br /&gt;
                x: normalizeNumber(node.getAttribute(&#039;data-decoration-source-x&#039;), 0),&lt;br /&gt;
                y: normalizeNumber(node.getAttribute(&#039;data-decoration-source-y&#039;), 0)&lt;br /&gt;
            }, well);&lt;br /&gt;
&lt;br /&gt;
            node.style.left = position.x + &#039;px&#039;;&lt;br /&gt;
            node.style.top = position.y + &#039;px&#039;;&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-x&#039;, String(position.x));&lt;br /&gt;
            node.setAttribute(&#039;data-decoration-local-y&#039;, String(position.y));&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleMainPageDecorationRebase() {&lt;br /&gt;
        if (scheduledMainPageRebase) return;&lt;br /&gt;
        scheduledMainPageRebase = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledMainPageRebase = 0;&lt;br /&gt;
            rebaseLegacyMainPageDecorations();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindMainPagePlacementRefresh() {&lt;br /&gt;
        var portal = document.querySelector(&#039;.main-portal&#039;);&lt;br /&gt;
        var nodes;&lt;br /&gt;
&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039; || !portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = null;&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (observedMainPagePortal !== portal) {&lt;br /&gt;
            if (mainPagePlacementObserver) mainPagePlacementObserver.disconnect();&lt;br /&gt;
            if (mainPagePlacementMutationObserver) mainPagePlacementMutationObserver.disconnect();&lt;br /&gt;
            observedMainPagePortal = portal;&lt;br /&gt;
&lt;br /&gt;
            if (typeof ResizeObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementObserver = new ResizeObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                nodes = [&lt;br /&gt;
                    document.querySelector(&#039;.liberty-content-main&#039;),&lt;br /&gt;
                    portal,&lt;br /&gt;
                    portal.querySelector(&#039;[data-component=&amp;quot;category-nav&amp;quot;]&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-panel&#039;),&lt;br /&gt;
                    portal.querySelector(&#039;.main-body-well&#039;)&lt;br /&gt;
                ];&lt;br /&gt;
                nodes.forEach(function (node) {&lt;br /&gt;
                    if (node) mainPagePlacementObserver.observe(node);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
                mainPagePlacementMutationObserver = new MutationObserver(scheduleMainPageDecorationRebase);&lt;br /&gt;
                mainPagePlacementMutationObserver.observe(portal, { childList: true, subtree: true });&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        scheduleMainPageDecorationRebase();&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 80);&lt;br /&gt;
        window.setTimeout(scheduleMainPageDecorationRebase, 250);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderForCurrentLayout(registry, token) {&lt;br /&gt;
        if (currentPageKey() !== &#039;대문&#039;) {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return Promise.resolve(registry);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return waitForMainPageDecorationLayout().then(function () {&lt;br /&gt;
            if (token === runtimeToken) render(registry);&lt;br /&gt;
            return registry;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(registry) {&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        lastRegistry = registry || { decorations: [] };&lt;br /&gt;
        lastRenderedPageKey = currentPageKey();&lt;br /&gt;
        bindNationsPlacementRefresh();&lt;br /&gt;
        clearRendered(document);&lt;br /&gt;
        decorationList(lastRegistry).forEach(function (entry) {&lt;br /&gt;
            if (token === runtimeToken &amp;amp;&amp;amp; matchesPage(entry)) applyDecoration(entry);&lt;br /&gt;
        });&lt;br /&gt;
        bindMainPagePlacementRefresh();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function renderPrepared() {&lt;br /&gt;
        var registry = getPreparedRegistrySync() || lastRegistry;&lt;br /&gt;
        var token = runtimeToken;&lt;br /&gt;
        if (!registry) return false;&lt;br /&gt;
        renderForCurrentLayout(registry, token);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function syncDecorationState() {&lt;br /&gt;
        if (lastRenderedPageKey === currentPageKey() &amp;amp;&amp;amp; hasRenderedDecorations(document)) {&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        if (lastRegistry) {&lt;br /&gt;
            renderForCurrentLayout(lastRegistry, runtimeToken);&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        return renderPrepared();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleRenderFromCache() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (!syncDecorationState()) reload();&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function scheduleDecorationVisibilityUpdate() {&lt;br /&gt;
        if (scheduledRender) return;&lt;br /&gt;
        scheduledRender = window.requestAnimationFrame ? window.requestAnimationFrame(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }) : window.setTimeout(function () {&lt;br /&gt;
            scheduledRender = 0;&lt;br /&gt;
            if (lastRenderedPageKey !== currentPageKey() || !hasRenderedDecorations(document)) {&lt;br /&gt;
                if (!syncDecorationState()) reload();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            updateDecorationVisibility(document);&lt;br /&gt;
        }, 0);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function bindNationsPlacementRefresh() {&lt;br /&gt;
        var stack = document.querySelector(&#039;.clbi-nations-panel-stack&#039;);&lt;br /&gt;
&lt;br /&gt;
        if (!stack || observedNationsStack === stack) return;&lt;br /&gt;
        observedNationsStack = stack;&lt;br /&gt;
&lt;br /&gt;
        if (nationsPlacementObserver) {&lt;br /&gt;
            nationsPlacementObserver.disconnect();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (typeof MutationObserver === &#039;function&#039;) {&lt;br /&gt;
            nationsPlacementObserver = new MutationObserver(scheduleDecorationVisibilityUpdate);&lt;br /&gt;
            nationsPlacementObserver.observe(stack, {&lt;br /&gt;
                subtree: true,&lt;br /&gt;
                attributes: true,&lt;br /&gt;
                attributeFilter: [&#039;class&#039;, &#039;hidden&#039;, &#039;aria-selected&#039;, &#039;data-current-era&#039;, &#039;data-nations-current-era&#039;, &#039;data-era-year&#039;]&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function getPreparedRegistrySync() {&lt;br /&gt;
        var registry = null;&lt;br /&gt;
        var url;&lt;br /&gt;
        try {&lt;br /&gt;
            if (window.EntryStore &amp;amp;&amp;amp; typeof window.EntryStore.getJsonSync === &#039;function&#039;) {&lt;br /&gt;
                registry = window.EntryStore.getJsonSync(REGISTRY_TITLE);&lt;br /&gt;
                if (!registry &amp;amp;&amp;amp; mw &amp;amp;&amp;amp; mw.util &amp;amp;&amp;amp; typeof mw.util.getUrl === &#039;function&#039;) {&lt;br /&gt;
                    url = mw.util.getUrl(REGISTRY_TITLE, { action: &#039;raw&#039;, ctype: &#039;application/json&#039; });&lt;br /&gt;
                    registry = window.EntryStore.getJsonSync(url);&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        } catch (err) {}&lt;br /&gt;
        return registry &amp;amp;&amp;amp; typeof registry === &#039;object&#039; ? registry : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fetchRegistry() {&lt;br /&gt;
        var url;&lt;br /&gt;
        var prepared = getPreparedRegistrySync();&lt;br /&gt;
        if (prepared) return Promise.resolve(prepared);&lt;br /&gt;
        if (!mw || !mw.util || typeof fetch !== &#039;function&#039;) return Promise.resolve({ decorations: [] });&lt;br /&gt;
        url = mw.util.getUrl(REGISTRY_TITLE, {&lt;br /&gt;
            action: &#039;raw&#039;,&lt;br /&gt;
            ctype: &#039;application/json&#039;&lt;br /&gt;
        });&lt;br /&gt;
        return fetch(url, { credentials: &#039;same-origin&#039;, cache: &#039;force-cache&#039; })&lt;br /&gt;
            .then(function (response) {&lt;br /&gt;
                if (!response.ok) throw new Error(&#039;HTTP &#039; + response.status);&lt;br /&gt;
                return response.text();&lt;br /&gt;
            })&lt;br /&gt;
            .then(function (text) {&lt;br /&gt;
                if (!text || !text.trim()) return { decorations: [] };&lt;br /&gt;
                return JSON.parse(text);&lt;br /&gt;
            })&lt;br /&gt;
            .catch(function () {&lt;br /&gt;
                return { decorations: [] };&lt;br /&gt;
            });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function reload() {&lt;br /&gt;
        var token;&lt;br /&gt;
        runtimeToken += 1;&lt;br /&gt;
        token = runtimeToken;&lt;br /&gt;
        return fetchRegistry().then(function (registry) {&lt;br /&gt;
            return preloadPixelAssetsForRegistry(registry).then(function () {&lt;br /&gt;
                return renderForCurrentLayout(registry, token);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, function (event) {&lt;br /&gt;
        var target = event.target &amp;amp;&amp;amp; event.target.closest ? event.target.closest(&#039;.clbi-nations-tabpanel-tab[data-continent], [data-nations-era-move]&#039;) : null;&lt;br /&gt;
        if (target) window.setTimeout(scheduleDecorationVisibilityUpdate, 0);&lt;br /&gt;
    }, true);&lt;br /&gt;
&lt;br /&gt;
    function decorationDiagnostics() {&lt;br /&gt;
        return {&lt;br /&gt;
            build: &#039;20260713-main-body-well-coordinate-003&#039;,&lt;br /&gt;
            hasRegistry: !!lastRegistry,&lt;br /&gt;
            entries: decorationList(lastRegistry).length,&lt;br /&gt;
            rendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            preparedRegistry: !!getPreparedRegistrySync(),&lt;br /&gt;
            pixelAssets: Object.keys(pixelAssetCache || {}).length,&lt;br /&gt;
            pixelCanvases: Object.keys(pixelCanvasCache || {}).length,&lt;br /&gt;
            visibilityOnlySync: true,&lt;br /&gt;
            mainPageCoordinateSurface: MAIN_PAGE_TARGET,&lt;br /&gt;
            mainPagePlacementObserved: !!observedMainPagePortal,&lt;br /&gt;
            lastRenderedPage: lastRenderedPageKey,&lt;br /&gt;
            visibleRendered: document.querySelectorAll(&#039;[&#039; + RENDERED_ATTR + &#039;=&amp;quot;1&amp;quot;][data-decoration-visible=&amp;quot;1&amp;quot;]&#039;).length,&lt;br /&gt;
            scheduled: !!scheduledRender&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    window.Decorations = window.Decorations || {};&lt;br /&gt;
    window.Decorations.reload = reload;&lt;br /&gt;
    window.Decorations.render = render;&lt;br /&gt;
    window.Decorations.renderPrepared = renderPrepared;&lt;br /&gt;
    window.Decorations.sync = syncDecorationState;&lt;br /&gt;
    window.Decorations.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.Decorations.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.Decorations.clear = clearRendered;&lt;br /&gt;
    window.Decorations.apply = applyDecoration;&lt;br /&gt;
    window.Decorations.pageKey = currentPageKey;&lt;br /&gt;
    window.Decorations.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.Decorations.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.Decorations.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.Decorations.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    window.CLBI_DECORATIONS = window.CLBI_DECORATIONS || {};&lt;br /&gt;
    window.CLBI_DECORATIONS.reload = reload;&lt;br /&gt;
    window.CLBI_DECORATIONS.render = render;&lt;br /&gt;
    window.CLBI_DECORATIONS.renderPrepared = renderPrepared;&lt;br /&gt;
    window.CLBI_DECORATIONS.sync = syncDecorationState;&lt;br /&gt;
    window.CLBI_DECORATIONS.updateVisibility = updateDecorationVisibility;&lt;br /&gt;
    window.CLBI_DECORATIONS.diagnostics = decorationDiagnostics;&lt;br /&gt;
    window.CLBI_DECORATIONS.clear = clearRendered;&lt;br /&gt;
    window.CLBI_DECORATIONS.apply = applyDecoration;&lt;br /&gt;
    window.CLBI_DECORATIONS.pageKey = currentPageKey;&lt;br /&gt;
    window.CLBI_DECORATIONS.loadPixelAsset = fetchPixelAsset;&lt;br /&gt;
    window.CLBI_DECORATIONS.preparePixelCanvas = preparePixelCanvas;&lt;br /&gt;
    window.CLBI_DECORATIONS.getPreparedPixelCanvasSync = getPreparedPixelCanvasSync;&lt;br /&gt;
    window.CLBI_DECORATIONS.drawPixelAssetToCanvas = drawPixelAssetToCanvas;&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;, reload);&lt;br /&gt;
    } else {&lt;br /&gt;
        reload();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
        mw.hook(&#039;wikipage.content&#039;).add(function () {&lt;br /&gt;
            reload();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
})(mediaWiki);&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Unified Shortcuts loader&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    if (!window.mw || !mw.loader) return;&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Nation_List_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:History_Event_Manager.js&#039;);&lt;br /&gt;
    loadClbiRawScript(&#039;MediaWiki:Shortcuts.js&#039;);&lt;br /&gt;
})();&lt;br /&gt;
&lt;br /&gt;
/* =========================================&lt;br /&gt;
   Main page manifesto bitmap renderer&lt;br /&gt;
   Integrated build: no extra raw-script page, no image/data-URL decoding.&lt;br /&gt;
   New shared systems do not use the legacy CLBI prefix.&lt;br /&gt;
   ========================================= */&lt;br /&gt;
(function (window, document, mw) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
&lt;br /&gt;
    /*&lt;br /&gt;
    Retired compatibility block&lt;br /&gt;
    -----------------------------------------&lt;br /&gt;
    선언문은 실제 HTML과 ManifestoIntro의 실선 요소만 사용한다.&lt;br /&gt;
    이 구식 렌더러는 제목·본문과 완성형 구분선을 canvas에 한 번에 그려&lt;br /&gt;
    새 애니메이션의 선 뒤에 트랙이 나타날 수 있었다.&lt;br /&gt;
&lt;br /&gt;
    기존 Common.js를 장기 캐시한 뒤 새 파일로 넘어오는 경우를 위해&lt;br /&gt;
    남아 있는 canvas와 상태만 제거하고 즉시 종료한다. 새 공개 이름에는&lt;br /&gt;
    프로젝트 접두사를 사용하지 않는 규칙을 유지한다.&lt;br /&gt;
    */&lt;br /&gt;
    function removeRetiredBitmap() {&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-bitmap&#039;),&lt;br /&gt;
            function (canvas) {&lt;br /&gt;
                if (canvas.parentNode) canvas.parentNode.removeChild(canvas);&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        Array.prototype.forEach.call(&lt;br /&gt;
            document.querySelectorAll(&#039;.main-portal .main-manifesto-inner&#039;),&lt;br /&gt;
            function (inner) {&lt;br /&gt;
                inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;data-bitmap-width&#039;,&lt;br /&gt;
                    &#039;data-bitmap-optical-shift-x&#039;,&lt;br /&gt;
                    &#039;data-bitmap-state&#039;,&lt;br /&gt;
                    &#039;data-bitmap-error&#039;&lt;br /&gt;
                ].forEach(function (name) {&lt;br /&gt;
                    inner.removeAttribute(name);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    removeRetiredBitmap();&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(removeRetiredBitmap);&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreRetiredBitmapHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:&#039;retired-20260713-divider-no-track-001&#039;,&lt;br /&gt;
        recalculate:removeRetiredBitmap,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            return {&lt;br /&gt;
                integrated:false,&lt;br /&gt;
                state:&#039;retired&#039;,&lt;br /&gt;
                canvas:!!document.querySelector(&#039;.main-portal .main-manifesto-bitmap&#039;)&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    return;&lt;br /&gt;
&lt;br /&gt;
    var VERSION = &#039;20260712-bitmap18-inline-002&#039;;&lt;br /&gt;
    var SELECTOR = &#039;.main-portal .main-manifesto-inner&#039;;&lt;br /&gt;
    var CELL_W = 24;&lt;br /&gt;
    var CELL_H = 24;&lt;br /&gt;
    var GLYPHS = {&amp;quot;진&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,25056,25072,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;창&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,13280,78328,258272,12768,14128,13848,4360,3840,7040,12672,4480,8064,3840,0,0],&amp;quot;바&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12672,12672,14080,13056,13064,258456,127472,12784,12504,12408,12312,12288,12288,4096,0,0],&amp;quot;다&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,12792,12400,12320,258096,128528,14328,13048,12304,12288,12288,12288,4096,0,0],&amp;quot;는&amp;quot;:[19,0,0,0,0,0,0,96,192,192,14528,16064,960,0,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;왕&amp;quot;:[19,0,0,0,0,0,12288,28768,25568,25456,25392,25392,254944,24960,16064,10236,12344,7680,14080,12672,12672,16128,7936,0,0],&amp;quot;과&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,26560,26352,26112,26112,26208,255680,254912,29248,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;노&amp;quot;:[19,0,0,0,0,0,0,0,0,96,192,192,4288,15552,16320,7040,7168,3072,50688,131064,124,0,0,0,0],&amp;quot;예&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,24048,24504,23320,23320,24504,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;를&amp;quot;:[19,0,0,0,0,0,7168,16320,6144,8128,448,15552,1984,122880,262140,16636,8128,8128,3840,8064,1792,960,65408,4096,0],&amp;quot;같&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,110976,127168,12384,12344,4104,3584,0,8064,1920,256,14720,8064,0,0],&amp;quot;은&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,130944,115708,16,448,448,128,128,16256,7936,0,0],&amp;quot;깊&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,25024,24800,24624,8216,12288,16256,4864,15872,7680,31232,32640,256,0],&amp;quot;이&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,25072,25584,25368,25368,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;로&amp;quot;:[19,0,0,0,0,0,0,0,15360,16320,6272,7168,8128,384,14528,16320,7040,3072,50688,131064,124,0,0,0,0],&amp;quot;삼&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,78064,258272,13296,14136,13324,4096,16256,16256,4224,4480,8064,8064,0,0],&amp;quot;켰&amp;quot;:[19,0,0,0,0,0,12288,24576,26112,26600,29560,32656,25072,31992,26720,8224,8216,13056,15104,5056,15232,28544,58992,0,0],&amp;quot;고&amp;quot;:[19,0,0,0,0,0,0,0,0,7680,16352,12416,12288,12288,12544,13056,6912,4864,49920,131064,124,0,0,0,0],&amp;quot;,&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,8,0,0,0],&amp;quot;겨&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,32560,31104,24768,31840,31792,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;울&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,114688,262140,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;사&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12352,12480,12480,12480,12400,258272,127920,14104,13828,13312,12288,12288,12288,4096,0,0],&amp;quot;람&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13296,13240,78208,258552,12304,14224,13296,4096,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;짐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,26592,26160,9756,0,32512,32640,12672,12672,16128,16128,0,0],&amp;quot;승&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,130816,115708,272,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;의&amp;quot;:[19,0,0,0,0,0,28672,57344,24608,25536,26592,26160,26160,26464,25568,24576,24576,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;살&amp;quot;:[19,0,0,0,0,0,6144,12288,12352,12736,12480,110832,127456,13232,13848,5132,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;을&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6272,7552,3840,32768,130944,115708,0,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;빛&amp;quot;:[19,0,0,0,0,0,12288,24576,25344,25344,26376,25496,25592,25584,25552,25088,7168,12288,32640,6400,7680,32256,25472,0,0],&amp;quot;으&amp;quot;:[19,0,0,0,0,0,0,0,128,3968,8064,4544,4288,6336,7616,3968,0,0,57344,131068,56,0,0,0,0],&amp;quot;얼&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25008,32536,31512,25016,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;렸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,32568,27008,31224,31792,26544,9200,8192,13056,15104,4928,15232,28544,58976,0,0],&amp;quot;.&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,12,0,0,0,0],&amp;quot;그&amp;quot;:[19,0,0,0,0,0,0,0,0,7168,16352,12672,12288,12288,12288,12288,6144,4096,57344,131068,56,0,0,0,0],&amp;quot;들&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130048,118780,120,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;돌&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,8128,130048,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;벽&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25344,32520,31512,25560,32632,25584,9104,8192,31744,32640,8192,8192,8192,8192,0,0],&amp;quot;세&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22720,22720,22752,24432,22752,22960,23320,23300,22528,18432,16384,16384,24576,0,0],&amp;quot;우&amp;quot;:[19,0,0,0,0,0,0,384,7936,6528,12480,6336,7552,3968,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;땅&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,28560,9200,74160,254128,30936,16376,12280,12544,7936,13184,12672,12672,8064,3840,0,0],&amp;quot;찢&amp;quot;:[19,0,0,0,0,0,12288,24576,24704,26616,28656,26560,25456,28656,28056,26848,8192,32512,16128,7680,15872,25344,16768,0,0],&amp;quot;어&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,25520,32536,27416,25496,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;길&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,25472,24768,24672,24624,8216,16256,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;냈&amp;quot;:[19,0,0,0,0,0,8192,58368,19456,27672,27704,31792,31792,32560,28656,27120,8704,13824,14080,13248,15232,28544,50272,0,0],&amp;quot;며&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,26600,32536,25368,25400,32560,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;쇠&amp;quot;:[19,0,0,0,0,0,28672,57344,24704,24960,24960,25056,25536,26208,27888,24960,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;에&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22544,23024,23480,24344,24344,22968,23024,22528,22528,18432,16384,16384,24576,0,0],&amp;quot;제&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23488,23032,22976,24512,24184,23024,23344,23320,23052,18432,16384,16384,24576,0,0],&amp;quot;름&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,384,130560,116732,56,15872,16320,4288,4544,7552,8064,0,0],&amp;quot;새&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,27840,27840,31968,31856,23776,23984,20248,20228,16384,16384,16384,16384,24576,0,0],&amp;quot;겼&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,30712,32640,25024,31968,26672,8216,8200,15104,15104,6976,15232,28608,58992,0,0],&amp;quot;감&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,4120,4096,16256,16320,4224,4480,8064,8064,0,0],&amp;quot;옥&amp;quot;:[19,0,0,0,0,0,1792,7936,6528,6272,6528,8064,3584,1536,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;되&amp;quot;:[19,0,0,0,0,0,28672,57344,25088,26608,25072,24672,25632,26416,26608,25456,24960,32704,25596,24576,24576,24576,8192,0,0],&amp;quot;군&amp;quot;:[19,0,0,0,0,0,0,7680,16352,12288,12288,12288,6144,126976,262140,18172,1536,1728,1472,192,192,16256,8064,0,0],&amp;quot;대&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28032,28144,31856,31776,23600,24344,20440,19960,16400,16384,16384,16384,24576,0,0],&amp;quot;나&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12296,12312,12336,12304,259600,128784,14256,12528,12320,12288,12288,12288,4096,0,0],&amp;quot;르&amp;quot;:[19,0,0,0,0,0,0,0,6144,16320,6336,7168,8128,448,12480,16320,3968,0,57344,131068,56,0,0,0,0],&amp;quot;오&amp;quot;:[19,0,0,0,0,0,0,0,256,7936,15232,12672,12416,12672,8064,7936,3072,1024,49664,131064,124,0,0,0,0],&amp;quot;래&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28144,27064,32128,32248,23664,23568,20240,20464,16880,16384,16384,16384,24576,0,0],&amp;quot;된&amp;quot;:[19,0,0,0,0,0,28672,57344,26368,25584,24800,25696,28656,26608,29440,32736,25080,24832,25344,768,768,65280,15872,0,0],&amp;quot;원&amp;quot;:[19,0,0,0,0,0,28672,57440,25536,26464,26160,26160,29664,32640,26592,26620,25360,25024,25536,768,768,65280,15872,0,0],&amp;quot;한&amp;quot;:[19,0,0,0,0,0,6144,28704,12512,12800,14328,12784,258528,13088,12592,12784,12512,12416,4544,384,128,16256,7936,0,0],&amp;quot;함&amp;quot;:[19,0,0,0,0,0,6144,12512,12480,12800,14328,78032,258528,13104,12720,12784,4192,15744,16256,4288,4480,8064,8064,0,0],&amp;quot;께&amp;quot;:[19,0,0,0,0,0,24576,55296,55296,55296,55488,57336,57280,57184,57184,55600,55704,55500,55392,49152,49152,16384,16384,0,0],&amp;quot;음&amp;quot;:[19,0,0,0,0,0,1920,8064,6528,6336,6336,3968,1792,32768,131040,16892,0,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;손&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,26304,1584,130560,249852,120,448,448,128,128,16256,7936,0,0],&amp;quot;넘&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,31792,31792,24624,28208,26608,8416,0,32512,32640,12672,12672,16256,15616,0,0],&amp;quot;갔&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,78208,258240,12384,12336,12312,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;\n&amp;quot;:[8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],&amp;quot;줄&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,3840,15296,12480,41056,130976,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;처&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,25072,32448,27616,25392,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;부&amp;quot;:[19,0,0,0,0,0,6144,12288,28768,14400,15552,13248,8128,7872,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;터&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25056,24768,25344,25584,31984,31840,25648,28568,26360,24624,24576,24576,8192,8192,0,0],&amp;quot;목&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;둘&amp;quot;:[19,0,0,0,0,0,6144,16320,896,384,14528,16320,960,130944,116732,1592,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;레&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23024,22968,22912,24568,22640,22544,23312,23536,23024,18432,16384,16384,24576,0,0],&amp;quot;있&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,25400,25368,25368,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;었&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32568,32536,25496,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;끝&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,3840,3584,8128,1920,384,16320,4032,0,0],&amp;quot;속&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,7936,12672,26336,1568,130560,116732,56,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;뿌&amp;quot;:[19,0,0,0,0,0,4096,30720,14080,15904,16352,13280,16320,14208,4608,130944,126972,1552,1536,1536,1536,1536,512,0,0],&amp;quot;리&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,24960,25040,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;와&amp;quot;:[19,0,0,0,0,0,12288,28672,24608,25568,25456,26160,26416,25584,254432,254336,28864,32736,25084,8192,8192,8192,8192,0,0],&amp;quot;빨&amp;quot;:[19,0,0,0,0,0,12288,28672,26112,26176,26304,255944,255992,10200,10224,1728,16256,16256,7936,7936,1792,896,65024,0,0],&amp;quot;굶&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,118784,262140,18428,1536,14080,16368,13792,14272,14272,16352,16352,0,0],&amp;quot;주&amp;quot;:[19,0,0,0,0,0,0,14336,16320,3264,1792,7936,12736,24672,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;림&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25584,25400,25472,25072,24624,26544,9200,8192,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;매&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28544,28644,32156,32024,23832,23992,19952,19504,16384,16384,16384,16384,24576,0,0],&amp;quot;였&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,32752,32568,25368,32664,25072,8416,8192,15104,15104,7104,14720,28608,26224,0,0],&amp;quot;른&amp;quot;:[19,0,0,0,0,0,4096,16320,6336,7744,4032,14528,16320,896,126976,253948,120,64,448,128,128,16256,7936,0,0],&amp;quot;아&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,12304,12528,12720,13080,258840,127896,12784,12352,12288,12288,12288,12288,4096,0,0],&amp;quot;직&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,24992,25072,25568,26160,9752,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;태&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27840,28032,32240,31856,23600,24336,20440,20472,16432,16384,16384,16384,24576,0,0],&amp;quot;지&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25584,24992,24768,24816,25568,26160,27672,25612,24576,24576,24576,8192,0,0],&amp;quot;않&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,7168,6240,32352,15456,13408,14176,8160,0,0],&amp;quot;자&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13304,12720,12480,258296,127984,14128,13848,13324,12288,12288,12288,4096,0,0],&amp;quot;졌&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,32752,31136,32240,32736,26160,9756,8704,13056,15104,4928,15232,28544,50784,0,0],&amp;quot;앞&amp;quot;:[19,0,0,0,0,0,6144,12288,12304,12528,12784,111384,258840,13208,12784,4160,6144,8128,2432,7936,2816,15616,16320,128,0],&amp;quot;갈&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13304,127360,127168,12384,12344,4104,16256,8064,3840,8064,1792,896,65280,0,0],&amp;quot;때&amp;quot;:[19,0,0,0,0,0,24576,52224,55296,55296,57096,56312,63920,63632,63704,56536,57336,57336,49216,49152,49152,16384,16384,0,0],&amp;quot;마&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13184,13284,13084,13080,258840,127792,12784,12464,12288,12288,12288,12288,4096,0,0],&amp;quot;듭&amp;quot;:[19,0,0,0,0,0,6144,16320,1984,384,12480,16064,3008,32768,131008,115708,14336,12352,12480,14720,16256,8064,7808,0,0],&amp;quot;파&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13248,12792,12688,13296,258528,127392,16352,12412,12288,12288,12288,12288,4096,0,0],&amp;quot;각&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,12672,258240,12384,12336,4120,4096,16128,13248,12288,4096,4096,4096,0,0],&amp;quot;쥔&amp;quot;:[19,0,0,0,0,0,28672,57856,26608,25584,25024,26592,28208,27664,28544,28668,25360,24960,25536,768,768,65280,15872,0,0],&amp;quot;당&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,12536,77920,258096,15888,14328,12920,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;기&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25344,26616,25392,24960,24768,24672,24624,24600,24584,24576,24576,24576,8192,0,0],&amp;quot;면&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26604,32536,25368,32560,31728,25072,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;풀&amp;quot;:[19,0,0,0,0,0,12288,16352,7296,8064,3328,32704,41440,131008,116732,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;려&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,32696,31104,25040,31856,27696,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;날&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12312,12344,110640,128560,14256,13296,4192,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;것&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25592,24960,32704,26848,24624,24600,11272,3072,1792,3584,15104,29056,24800,0,0],&amp;quot;라&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13280,12728,12672,12792,258168,127504,13840,14320,12784,12288,12288,12288,4096,0,0],&amp;quot;여&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24592,24816,32688,25368,25368,32664,25072,24640,24576,24576,24576,8192,8192,0,0],&amp;quot;므&amp;quot;:[19,0,0,0,0,0,0,0,4096,31744,32736,6624,3264,1728,16064,7360,0,0,57344,131068,56,0,0,0,0],&amp;quot;번&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25344,26376,26392,32728,25464,25584,25552,24576,25344,8960,768,768,32512,15872,0,0],&amp;quot;도&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,4064,960,384,14528,16064,7136,7360,3072,50688,131064,124,0,0,0,0],&amp;quot;느&amp;quot;:[19,0,0,0,0,0,0,0,0,32,192,192,192,14528,16064,7104,0,0,57344,131068,56,0,0,0,0],&amp;quot;슨&amp;quot;:[19,0,0,0,0,0,512,1536,1536,1920,7936,14720,24768,48,130048,118780,56,448,448,128,128,16256,7936,0,0],&amp;quot;해&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19680,27968,28664,32240,32224,23984,23984,19952,19568,16384,16384,16384,16384,24576,0,0],&amp;quot;았&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12528,12784,78616,258840,13208,12784,12512,4352,15104,6912,6528,7616,30656,25136,0,0],&amp;quot;침&amp;quot;:[19,0,0,0,0,0,12288,24768,25024,25088,26592,25584,24800,25568,26416,9232,8,15616,32640,12672,12672,16256,16128,0,0],&amp;quot;내&amp;quot;:[19,0,0,0,0,0,24576,58368,19456,19456,27656,27672,31792,31792,31760,24368,20464,19952,16384,16384,24576,24576,8192,0,0],&amp;quot;멎&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25576,32536,31512,25392,25072,8224,8192,32640,6912,3584,15872,25344,16768,0,0],&amp;quot;서&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,32496,24800,25520,26392,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;긴&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26368,25592,25520,25024,24768,24688,24632,24584,25472,8960,768,768,32512,15872,0,0],&amp;quot;붙&amp;quot;:[19,0,0,0,0,0,6144,12288,14432,16064,13248,7872,38912,130816,116732,1552,3840,3584,8064,1920,384,15808,4032,0,0],&amp;quot;잡&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13304,111040,258288,13280,13872,5660,4096,12352,12416,14720,16256,8064,7296,0,0],&amp;quot;피&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,26496,25592,25392,25568,25440,24992,28640,26744,24576,24576,24576,24576,8192,0,0],&amp;quot;가&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,12288,13056,13304,12728,12736,127168,258144,12336,12312,12300,12288,12288,12288,4096,0,0],&amp;quot;굴&amp;quot;:[19,0,0,0,0,0,0,16128,13248,12288,12288,6144,126976,262140,18172,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;보&amp;quot;:[19,0,0,0,0,0,0,0,6144,12288,28768,14528,16064,13248,16320,7360,3072,1024,50688,131064,124,0,0,0,0],&amp;quot;하&amp;quot;:[19,0,0,0,0,0,6144,12288,12320,12512,12800,14332,12792,12512,259040,127792,12720,12528,12288,12288,12288,12288,4096,0,0],&amp;quot;입&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25584,25368,25368,25496,25072,8256,4096,28800,24960,14720,16128,16128,15616,0,0],&amp;quot;말&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13184,13308,111384,127768,13240,12784,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;했&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,28664,27888,32224,23984,19888,18672,96,13824,15872,13184,15232,28544,50272,0,0],&amp;quot;조&amp;quot;:[19,0,0,0,0,0,0,0,0,15360,8128,3072,1792,16128,28864,25184,1568,1536,50688,131064,124,0,0,0,0],&amp;quot;올&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6528,8064,3840,1536,128768,253948,16504,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;수&amp;quot;:[19,0,0,0,0,0,0,1536,3072,1792,1792,6912,29056,24800,32,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;록&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,16320,3968,128512,253948,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;열&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,32688,31512,25368,32696,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;단&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,12792,12400,258096,13872,14232,14328,12336,12672,4480,384,384,16256,7936,0,0],&amp;quot;질&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,25584,24992,25072,26464,26160,9240,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;히&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25024,26112,26616,25584,25024,25440,25392,25392,25056,24576,24576,24576,8192,8192,0,0],&amp;quot;운&amp;quot;:[19,0,0,0,0,0,384,3968,6528,6336,6272,3968,1792,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;데&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,22912,23024,22640,24352,22576,23320,23512,23032,22544,18432,16384,16384,24576,0,0],&amp;quot;끌&amp;quot;:[19,0,0,0,0,0,0,14720,16368,13152,12672,12672,4480,130560,116732,56,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;린&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25568,25464,24960,25592,24624,26416,26608,24672,8320,9088,256,256,32512,15872,0,0],&amp;quot;게&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,22960,24000,24512,22624,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;누&amp;quot;:[19,0,0,0,0,0,0,96,192,192,192,15552,16320,960,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;인&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24624,25072,25400,25368,25368,25072,24800,24576,9088,8960,256,256,32512,15872,0,0],&amp;quot;쳐&amp;quot;:[19,0,0,0,0,0,12288,24576,24800,25024,25344,26592,32752,31936,25568,32560,25360,24840,24832,24960,24960,8320,8192,0,0],&amp;quot;문&amp;quot;:[19,0,0,0,0,0,12288,15872,15328,6336,3264,7872,7360,32768,131040,17916,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;몸&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,7296,3072,130816,116732,56,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;식&amp;quot;:[19,0,0,0,0,0,12288,24576,24768,24960,25024,24816,24800,25568,26160,9244,0,31744,32640,8192,8192,12288,12288,0,0],&amp;quot;억&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24816,25072,32536,32536,25368,25072,8416,0,31744,32640,12288,12288,12288,12288,0,0],&amp;quot;전&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25592,24992,31984,25568,26160,26136,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;품&amp;quot;:[19,0,0,0,0,0,12288,16352,7360,8064,7040,16128,26592,127040,253948,18040,1536,16128,16320,4288,4544,7552,8064,0,0],&amp;quot;발&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,111512,127992,13176,13272,4096,8064,8064,3840,8064,1792,896,65024,0,0],&amp;quot;머&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25472,25576,25368,32536,27448,25392,25584,25056,24576,24576,24576,24576,8192,0,0],&amp;quot;두&amp;quot;:[19,0,0,0,0,0,0,14336,8128,960,384,12480,16064,5056,192,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;닐&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24600,24624,24624,27696,28464,26608,8288,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;격&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,26616,32640,25024,31968,26672,8216,8200,32256,26496,8192,8192,8192,8192,0,0],&amp;quot;없&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25072,32536,31512,25496,25072,8256,8192,13824,15936,4928,15296,28608,51008,0,0],&amp;quot;니&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,24584,24624,24624,24624,25648,28208,26544,25328,24576,24576,24576,8192,8192,0,0],&amp;quot;산&amp;quot;:[19,0,0,0,0,0,6144,28672,12352,12480,12480,12496,258272,13280,14128,13852,12288,12672,4480,384,384,16256,7936,0,0],&amp;quot;복&amp;quot;:[19,0,0,0,0,0,14336,12288,28736,15552,14272,16320,7808,3072,130560,249852,120,15872,16320,4096,4096,4096,6144,0,0],&amp;quot;종&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,26208,1568,130816,116732,280,3840,7040,4224,6272,8064,3840,0,0],&amp;quot;죽&amp;quot;:[19,0,0,0,0,0,6144,16320,3520,1792,7936,12736,24672,122912,262140,18172,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;망&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,78620,258840,13112,12784,12336,4352,7936,7040,12672,4480,8064,3840,0,0],&amp;quot;베&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22912,22912,24448,24448,24456,24536,23024,23024,22776,22648,18456,16384,16384,24576,0,0],&amp;quot;막&amp;quot;:[19,0,0,0,0,0,6144,12288,12288,13248,13308,13084,258840,13080,12784,12464,4096,15872,16320,12288,4096,4096,4096,0,0],&amp;quot;무&amp;quot;:[19,0,0,0,0,0,0,30720,32608,15296,6336,3520,16320,15488,0,131008,116732,1552,1536,1536,1536,1536,512,0,0],&amp;quot;릎&amp;quot;:[19,0,0,0,0,0,6144,16320,6272,8128,960,14528,8128,0,130560,116732,56,16320,3456,8064,3840,16128,14272,0,0],&amp;quot;꿇&amp;quot;:[19,0,0,0,0,0,0,14720,16368,12640,12672,12672,118912,262140,18428,1536,7936,7152,32736,14784,11712,16352,7648,0,0],&amp;quot;까&amp;quot;:[19,0,0,0,0,0,12288,28672,24576,24576,24768,10232,10176,9824,254752,254768,8600,8388,8288,8192,8192,8192,12288,0,0],&amp;quot;놓&amp;quot;:[19,0,0,0,0,0,0,224,192,12480,15552,8128,7552,130048,253948,16760,1792,15872,16320,3584,6912,3328,1792,0,0],&amp;quot;러&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24576,25568,25528,29056,32720,24688,25648,26160,26608,25056,24576,24576,8192,8192,0,0],&amp;quot;재&amp;quot;:[19,0,0,0,0,0,24576,60416,23552,19456,28608,28152,32192,31936,23672,24048,20272,20248,16908,16384,16384,16384,24576,0,0],&amp;quot;받&amp;quot;:[19,0,0,0,0,0,6144,12288,12672,13056,14092,78744,259064,13176,13304,12672,0,7936,3584,768,24960,32640,1920,0,0],&amp;quot;묵&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,6336,3264,16064,3200,130048,118780,1592,1536,16128,15296,4096,4096,4096,4096,0,0],&amp;quot;스&amp;quot;:[19,0,0,0,0,0,0,0,512,1536,3584,1920,1792,7936,12672,24800,0,0,57344,131068,56,0,0,0,0],&amp;quot;릴&amp;quot;:[19,0,0,0,0,0,12288,24576,24832,25592,25360,25536,25072,25136,26608,8432,14464,16256,7936,16128,3840,1792,130560,0,0],&amp;quot;계&amp;quot;:[19,0,0,0,0,0,24576,60416,22528,22528,23424,23544,24496,24256,22720,24416,22576,22552,22536,18432,16384,16384,24576,0,0],&amp;quot;구&amp;quot;:[19,0,0,0,0,0,0,6144,16352,12736,12288,12288,12288,6144,4096,131056,67324,1536,1536,1536,1536,1536,512,0,0],&amp;quot;물&amp;quot;:[19,0,0,0,0,0,14336,32544,15328,3264,7872,16064,32768,131040,18428,1536,16320,8128,3840,8064,1920,896,65280,0,0],&amp;quot;못&amp;quot;:[19,0,0,0,0,0,14336,32512,15328,7360,3776,16064,7296,3072,130816,116732,1048,3584,1536,1792,7936,12480,8304,0,0],&amp;quot;시&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,24640,24768,25024,24768,24816,24800,25520,26424,26124,25600,24576,24576,8192,8192,0,0],&amp;quot;육&amp;quot;:[19,0,0,0,0,0,1792,8064,6528,6336,6528,3968,1536,130048,118780,6584,6528,16256,15296,4096,4096,4096,4096,0,0],&amp;quot;신&amp;quot;:[19,0,0,0,0,0,12288,24576,24640,25024,25024,24768,24800,25568,26416,25628,24576,9088,8960,768,256,32512,15872,0,0],&amp;quot;일&amp;quot;:[19,0,0,0,0,0,12288,24576,24592,24816,25584,25368,25368,25528,25072,8192,16256,16256,7680,16128,3584,1792,130560,0,0],&amp;quot;모&amp;quot;:[19,0,0,0,0,0,0,0,14336,32256,16352,6368,3264,7872,16064,6144,2048,1024,50688,131064,124,0,0,0,0],&amp;quot;행&amp;quot;:[19,0,0,0,0,0,24576,58368,23776,19776,20472,27888,32224,32176,23984,19696,25184,15872,30208,25344,25344,16128,7680,0,0],&amp;quot;절&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,25472,25584,32160,32752,26464,26160,9752,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;멸&amp;quot;:[19,0,0,0,0,0,12288,24576,24576,26496,32760,31512,25400,32688,25072,8224,16128,16128,7680,16128,3584,1792,130560,0,0],&amp;quot;끊&amp;quot;:[19,0,0,0,0,0,0,14592,16368,13280,12672,12672,12672,114688,262140,16636,7168,6240,32352,15456,13408,16224,7648,0,0],&amp;quot;순&amp;quot;:[19,0,0,0,0,0,512,3584,1536,1792,8064,29056,24800,32768,131008,116732,3072,1984,1472,128,192,16256,7936,0,0],&amp;quot;간&amp;quot;:[19,0,0,0,0,0,6144,28672,12288,13184,13304,12688,258240,12384,12336,12312,12296,12672,4480,384,384,16256,7936,0,0]};&lt;br /&gt;
    var BODY_SCALE = 1;&lt;br /&gt;
    var TITLE_SCALE = 2;&lt;br /&gt;
    var BODY_TRACKING = -2;&lt;br /&gt;
    var TITLE_TRACKING = 0;&lt;br /&gt;
    var SPACE_ADVANCE = Math.round(CELL_H * 0.5);&lt;br /&gt;
    var BODY_LINE_HEIGHT = CELL_H + 8;&lt;br /&gt;
    var PARAGRAPH_GAP = 20;&lt;br /&gt;
    var DIVIDER_Y = 52;&lt;br /&gt;
    var BODY_TOP = 68;&lt;br /&gt;
    var BODY_COLOR = &#039;#d8d8d8&#039;;&lt;br /&gt;
    var TITLE_COLOR = &#039;#ffffff&#039;;&lt;br /&gt;
    var DIVIDER_COLOR = &#039;#555555&#039;;&lt;br /&gt;
    var resizeTimer = 0;&lt;br /&gt;
    var observed = typeof WeakSet === &#039;function&#039; ? new WeakSet() : null;&lt;br /&gt;
    var resizeObserver = typeof ResizeObserver === &#039;function&#039; ? new ResizeObserver(function (entries) {&lt;br /&gt;
        entries.forEach(function (entry) { schedule(entry.target, false); });&lt;br /&gt;
    }) : null;&lt;br /&gt;
&lt;br /&gt;
    function glyphFor(character) {&lt;br /&gt;
        return Object.prototype.hasOwnProperty.call(GLYPHS, character) ? GLYPHS[character] : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function measureText(text, scale, tracking) {&lt;br /&gt;
        var width = 0;&lt;br /&gt;
        var glyphCount = 0;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                width += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            width += glyph[0] * scale;&lt;br /&gt;
            width += tracking * scale;&lt;br /&gt;
            glyphCount += 1;&lt;br /&gt;
        });&lt;br /&gt;
        if (glyphCount &amp;amp;&amp;amp; tracking) width -= tracking * scale;&lt;br /&gt;
        return Math.max(0, Math.round(width));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function breakLongToken(token, maxWidth) {&lt;br /&gt;
        var pieces = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
        Array.from(token).forEach(function (character) {&lt;br /&gt;
            var candidate = current + character;&lt;br /&gt;
            if (current &amp;amp;&amp;amp; measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;gt; maxWidth) {&lt;br /&gt;
                pieces.push(current);&lt;br /&gt;
                current = character;&lt;br /&gt;
            } else {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        if (current) pieces.push(current);&lt;br /&gt;
        return pieces;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wrapParagraph(text, maxWidth) {&lt;br /&gt;
        var words = String(text || &#039;&#039;).trim().split(/\s+/).filter(Boolean);&lt;br /&gt;
        var lines = [];&lt;br /&gt;
        var current = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
        words.forEach(function (word) {&lt;br /&gt;
            var candidate = current ? current + &#039; &#039; + word : word;&lt;br /&gt;
            var pieces;&lt;br /&gt;
            if (measureText(candidate, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = candidate;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            if (current) {&lt;br /&gt;
                lines.push(current);&lt;br /&gt;
                current = &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            if (measureText(word, BODY_SCALE, BODY_TRACKING) &amp;lt;= maxWidth) {&lt;br /&gt;
                current = word;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            pieces = breakLongToken(word, maxWidth);&lt;br /&gt;
            pieces.forEach(function (piece, index) {&lt;br /&gt;
                if (index === pieces.length - 1) current = piece;&lt;br /&gt;
                else lines.push(piece);&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        if (current) lines.push(current);&lt;br /&gt;
        return lines.length ? lines : [&#039;&#039;];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function calculateOpticalShift(lines, width) {&lt;br /&gt;
        var weightedCenter = 0;&lt;br /&gt;
        var totalWeight = 0;&lt;br /&gt;
        var maxLineWidth = 0;&lt;br /&gt;
        var shift;&lt;br /&gt;
        lines.forEach(function (line) {&lt;br /&gt;
            var lineWidth = line.width;&lt;br /&gt;
            var weight = Math.max(1, lineWidth) * BODY_LINE_HEIGHT;&lt;br /&gt;
            weightedCenter += (lineWidth / 2) * weight;&lt;br /&gt;
            totalWeight += weight;&lt;br /&gt;
            maxLineWidth = Math.max(maxLineWidth, lineWidth);&lt;br /&gt;
        });&lt;br /&gt;
        if (!totalWeight) return 0;&lt;br /&gt;
        shift = Math.round((width / 2) - (weightedCenter / totalWeight));&lt;br /&gt;
        return Math.max(0, Math.min(Math.max(0, width - maxLineWidth), shift));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawGlyph(context, glyph, x, y, scale) {&lt;br /&gt;
        var py;&lt;br /&gt;
        var px;&lt;br /&gt;
        var row;&lt;br /&gt;
        for (py = 0; py &amp;lt; CELL_H; py += 1) {&lt;br /&gt;
            row = glyph[py + 1] || 0;&lt;br /&gt;
            if (!row) continue;&lt;br /&gt;
            for (px = 0; px &amp;lt; CELL_W; px += 1) {&lt;br /&gt;
                if (row &amp;amp; (1 &amp;lt;&amp;lt; px)) {&lt;br /&gt;
                    context.fillRect(&lt;br /&gt;
                        Math.round(x + px * scale),&lt;br /&gt;
                        Math.round(y + py * scale),&lt;br /&gt;
                        scale,&lt;br /&gt;
                        scale&lt;br /&gt;
                    );&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function drawText(context, text, x, y, scale, tracking, color) {&lt;br /&gt;
        context.fillStyle = color;&lt;br /&gt;
        Array.from(String(text || &#039;&#039;)).forEach(function (character) {&lt;br /&gt;
            var glyph;&lt;br /&gt;
            if (character === &#039; &#039;) {&lt;br /&gt;
                x += SPACE_ADVANCE * scale;&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            glyph = glyphFor(character);&lt;br /&gt;
            if (!glyph) return;&lt;br /&gt;
            drawGlyph(context, glyph, x, y, scale);&lt;br /&gt;
            x += (glyph[0] + tracking) * scale;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function sourceContent(inner) {&lt;br /&gt;
        var titleNode = inner.querySelector(&#039;.main-manifesto-title&#039;);&lt;br /&gt;
        var paragraphs = [];&lt;br /&gt;
        Array.prototype.forEach.call(inner.children || [], function (child) {&lt;br /&gt;
            if (child &amp;amp;&amp;amp; child.tagName === &#039;P&#039;) {&lt;br /&gt;
                var value = String(child.textContent || &#039;&#039;).trim();&lt;br /&gt;
                if (value) paragraphs.push(value);&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        return {&lt;br /&gt;
            title: titleNode ? String(titleNode.textContent || &#039;&#039;).trim() : &#039;&#039;,&lt;br /&gt;
            paragraphs: paragraphs&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function ensureCanvas(inner) {&lt;br /&gt;
        var canvas = inner.querySelector(&#039;.main-manifesto-bitmap&#039;);&lt;br /&gt;
        if (!canvas) {&lt;br /&gt;
            canvas = document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            canvas.className = &#039;main-manifesto-bitmap&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
            canvas.setAttribute(&#039;data-bitmap-version&#039;, VERSION);&lt;br /&gt;
            inner.insertBefore(canvas, inner.firstChild);&lt;br /&gt;
        }&lt;br /&gt;
        return canvas;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function render(inner, force) {&lt;br /&gt;
        var width;&lt;br /&gt;
        var source;&lt;br /&gt;
        var paragraphs;&lt;br /&gt;
        var flatLines = [];&lt;br /&gt;
        var titleWidth;&lt;br /&gt;
        var height;&lt;br /&gt;
        var canvas;&lt;br /&gt;
        var context;&lt;br /&gt;
        var y;&lt;br /&gt;
        var opticalShift;&lt;br /&gt;
&lt;br /&gt;
        if (!inner || !inner.isConnected) return;&lt;br /&gt;
        width = Math.max(1, Math.floor(inner.clientWidth || inner.getBoundingClientRect().width || 0));&lt;br /&gt;
        if (width &amp;lt;= 1) {&lt;br /&gt;
            window.setTimeout(function () { schedule(inner, true); }, 60);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        if (!force &amp;amp;&amp;amp; Number(inner.getAttribute(&#039;data-bitmap-width&#039;)) === width &amp;amp;&amp;amp; inner.classList.contains(&#039;is-bitmap-ready&#039;)) return;&lt;br /&gt;
&lt;br /&gt;
        source = sourceContent(inner);&lt;br /&gt;
        if (!source.title || !source.paragraphs.length) {&lt;br /&gt;
            fail(inner, new Error(&#039;Manifesto source nodes were not found.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        paragraphs = source.paragraphs.map(function (paragraph) {&lt;br /&gt;
            return wrapParagraph(paragraph, width).map(function (line) {&lt;br /&gt;
                var item = { text:line, width:measureText(line, BODY_SCALE, BODY_TRACKING) };&lt;br /&gt;
                flatLines.push(item);&lt;br /&gt;
                return item;&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        titleWidth = measureText(source.title, TITLE_SCALE, TITLE_TRACKING);&lt;br /&gt;
        height = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, index) {&lt;br /&gt;
            height += lines.length * BODY_LINE_HEIGHT;&lt;br /&gt;
            if (index &amp;lt; paragraphs.length - 1) height += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
        height = Math.max(height, DIVIDER_Y + 2);&lt;br /&gt;
&lt;br /&gt;
        canvas = ensureCanvas(inner);&lt;br /&gt;
        canvas.width = width;&lt;br /&gt;
        canvas.height = Math.ceil(height);&lt;br /&gt;
        canvas.style.width = width + &#039;px&#039;;&lt;br /&gt;
        canvas.style.height = Math.ceil(height) + &#039;px&#039;;&lt;br /&gt;
        context = canvas.getContext(&#039;2d&#039;, { alpha:true });&lt;br /&gt;
        if (!context) {&lt;br /&gt;
            fail(inner, new Error(&#039;Canvas 2D context is unavailable.&#039;));&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        context.imageSmoothingEnabled = false;&lt;br /&gt;
        context.clearRect(0, 0, canvas.width, canvas.height);&lt;br /&gt;
&lt;br /&gt;
        drawText(&lt;br /&gt;
            context,&lt;br /&gt;
            source.title,&lt;br /&gt;
            Math.round((width - titleWidth) / 2),&lt;br /&gt;
            0,&lt;br /&gt;
            TITLE_SCALE,&lt;br /&gt;
            TITLE_TRACKING,&lt;br /&gt;
            TITLE_COLOR&lt;br /&gt;
        );&lt;br /&gt;
&lt;br /&gt;
        /* 완성형 구분선은 폐기했다. 실선 애니메이션은 ManifestoIntro만 담당한다. */&lt;br /&gt;
&lt;br /&gt;
        opticalShift = calculateOpticalShift(flatLines, width);&lt;br /&gt;
        y = BODY_TOP;&lt;br /&gt;
        paragraphs.forEach(function (lines, paragraphIndex) {&lt;br /&gt;
            lines.forEach(function (line) {&lt;br /&gt;
                drawText(context, line.text, opticalShift, y, BODY_SCALE, BODY_TRACKING, BODY_COLOR);&lt;br /&gt;
                y += BODY_LINE_HEIGHT;&lt;br /&gt;
            });&lt;br /&gt;
            if (paragraphIndex &amp;lt; paragraphs.length - 1) y += PARAGRAPH_GAP;&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-width&#039;, String(width));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-optical-shift-x&#039;, String(opticalShift));&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;ready&#039;);&lt;br /&gt;
        inner.classList.add(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fail(inner, error) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        inner.classList.remove(&#039;is-bitmap-ready&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-state&#039;, &#039;failed&#039;);&lt;br /&gt;
        inner.setAttribute(&#039;data-bitmap-error&#039;, String(error &amp;amp;&amp;amp; error.message ? error.message : error));&lt;br /&gt;
        try { console.error(&#039;[MainPageBitmap]&#039;, error); } catch (ignore) {}&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function schedule(inner, force) {&lt;br /&gt;
        if (!inner) return;&lt;br /&gt;
        window.requestAnimationFrame(function () {&lt;br /&gt;
            try { render(inner, force); }&lt;br /&gt;
            catch (error) { fail(inner, error); }&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mount(root) {&lt;br /&gt;
        var scope = root &amp;amp;&amp;amp; root.querySelectorAll ? root : document;&lt;br /&gt;
        var nodes = [];&lt;br /&gt;
        if (scope.matches &amp;amp;&amp;amp; scope.matches(SELECTOR)) nodes.push(scope);&lt;br /&gt;
        nodes = nodes.concat(Array.prototype.slice.call(scope.querySelectorAll(SELECTOR)));&lt;br /&gt;
        nodes.forEach(function (inner) {&lt;br /&gt;
            if (resizeObserver &amp;amp;&amp;amp; (!observed || !observed.has(inner))) {&lt;br /&gt;
                resizeObserver.observe(inner);&lt;br /&gt;
                if (observed) observed.add(inner);&lt;br /&gt;
            }&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function recalculate() {&lt;br /&gt;
        Array.prototype.forEach.call(document.querySelectorAll(SELECTOR), function (inner) {&lt;br /&gt;
            schedule(inner, true);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function boot() {&lt;br /&gt;
        mount(document);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 120);&lt;br /&gt;
        window.setTimeout(function () { mount(document); }, 500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) document.addEventListener(&#039;DOMContentLoaded&#039;, boot, { once:true });&lt;br /&gt;
    else boot();&lt;br /&gt;
&lt;br /&gt;
    window.addEventListener(&#039;resize&#039;, function () {&lt;br /&gt;
        window.clearTimeout(resizeTimer);&lt;br /&gt;
        resizeTimer = window.setTimeout(recalculate, 100);&lt;br /&gt;
    }, { passive:true });&lt;br /&gt;
&lt;br /&gt;
    try {&lt;br /&gt;
        if (mw &amp;amp;&amp;amp; mw.hook) {&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(function (content) {&lt;br /&gt;
                mount(content &amp;amp;&amp;amp; content[0] ? content[0] : document);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    } catch (ignoreHook) {}&lt;br /&gt;
&lt;br /&gt;
    window.MainPageBitmap = {&lt;br /&gt;
        version:VERSION,&lt;br /&gt;
        recalculate:recalculate,&lt;br /&gt;
        status:function () {&lt;br /&gt;
            var inner = document.querySelector(SELECTOR);&lt;br /&gt;
            return inner ? {&lt;br /&gt;
                integrated:true,&lt;br /&gt;
                state:inner.getAttribute(&#039;data-bitmap-state&#039;) || &#039;pending&#039;,&lt;br /&gt;
                error:inner.getAttribute(&#039;data-bitmap-error&#039;) || &#039;&#039;,&lt;br /&gt;
                width:inner.getAttribute(&#039;data-bitmap-width&#039;) || &#039;&#039;,&lt;br /&gt;
                opticalShiftX:inner.getAttribute(&#039;data-bitmap-optical-shift-x&#039;) || &#039;&#039;,&lt;br /&gt;
                canvas:!!inner.querySelector(&#039;.main-manifesto-bitmap&#039;)&lt;br /&gt;
            } : { integrated:true, state:&#039;not-mounted&#039; };&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    window.MainPageManifesto = window.MainPageManifesto || {};&lt;br /&gt;
    window.MainPageManifesto.recalculate = recalculate;&lt;br /&gt;
})(window, document, window.mw);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
	<entry>
		<id>https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4645</id>
		<title>미디어위키:Common.css</title>
		<link rel="alternate" type="text/html" href="https://clbiwiki.com/index.php?title=%EB%AF%B8%EB%94%94%EC%96%B4%EC%9C%84%ED%82%A4:Common.css&amp;diff=4645"/>
		<updated>2026-07-17T00:25:09Z</updated>

		<summary type="html">&lt;p&gt;Nxdsxn: Install package: clbiwiki-mainpage-attached-section-css-top-cover-20260717 / Common.css&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* =========================================&lt;br /&gt;
   COASTLINE: BLACK ICE - Common (Entry Point)&lt;br /&gt;
   ========================================= */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
Common.css는 CSS 로더 역할만 한다.&lt;br /&gt;
&lt;br /&gt;
정리 원칙&lt;br /&gt;
-----------------------------------------&lt;br /&gt;
- 공통 토큰과 전역 테마: Theme.css&lt;br /&gt;
- 틀과 문서 컴포넌트: Template.css, Components.css&lt;br /&gt;
- 아이콘: Icons.css&lt;br /&gt;
- 레이아웃: Layout.css&lt;br /&gt;
- 편집기: Editor.css&lt;br /&gt;
- 다이얼로그: Dialogs.css&lt;br /&gt;
- 좌측 언어 다이얼: Layout.css + Common.js&lt;br /&gt;
- 대문 전용: MainPage.css&lt;br /&gt;
- 테스트 전용: Test.css&lt;br /&gt;
&lt;br /&gt;
주의:&lt;br /&gt;
Test.css는 실험용이다.&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
@import url(&#039;https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.5.13/cropper.min.css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Template.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-era-page-hotfix-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Icons.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Layout.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Theme.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260712-yisunshin-galmuri-method-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Editor.css&amp;amp;action=raw&amp;amp;ctype=text/css&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Components.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Dialogs.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260711-existing-account-login-001&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:MainPage.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260717-portal-section-css-top-cover-040&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:DevTools.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:Nation_List_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix57&#039;);&lt;br /&gt;
@import url(&#039;/index.php?title=MediaWiki:History_Event_Manager.css&amp;amp;action=raw&amp;amp;ctype=text/css&amp;amp;v=20260710-foundation-nations-pass-fix53&#039;);&lt;/div&gt;</summary>
		<author><name>Nxdsxn</name></author>
	</entry>
</feed>