(Install package: clbiwiki-mainpage-nav-gap-weld-fix-20260712 / js/BottomGuideNav.js) |
(Install package: clbiwiki-mainpage-exact-u-weld-frame-20260712 / js/BottomGuideNav.js) |
||
| 16번째 줄: | 16번째 줄: | ||
var XLINK_NS = 'http://www.w3.org/1999/xlink'; | var XLINK_NS = 'http://www.w3.org/1999/xlink'; | ||
var COMPONENT = 'category-guide-nav'; | var COMPONENT = 'category-guide-nav'; | ||
var HEIGHT = | var ROW_HEIGHT = 36; | ||
var HEIGHT = 24; | |||
var FRAME = 4; | |||
var GAP = 2; | |||
var BUTTON_TOP = FRAME + GAP; | |||
var BUTTON_BOTTOM = BUTTON_TOP + HEIGHT; | |||
var SHARED_BOTTOM_TOP = BUTTON_BOTTOM + GAP; | |||
var OUTER_BORDER = 1; | var OUTER_BORDER = 1; | ||
var INSET_OFFSET = 1.5; | var INSET_OFFSET = 1.5; | ||
| 136번째 줄: | 142번째 줄: | ||
return line; | return line; | ||
} | |||
function createRect(className, x, y, width, height) { | |||
var rect = createSvgElement('rect'); | |||
rect.setAttribute('class', className); | |||
rect.setAttribute('x', roundCoordinate(x)); | |||
rect.setAttribute('y', roundCoordinate(y)); | |||
rect.setAttribute('width', roundCoordinate(width)); | |||
rect.setAttribute('height', roundCoordinate(height)); | |||
rect.setAttribute('pointer-events', 'none'); | |||
return rect; | |||
} | } | ||
| 141번째 줄: | 160번째 줄: | ||
var usableWidth = Math.max(320, Math.floor(width || 0)); | var usableWidth = Math.max(320, Math.floor(width || 0)); | ||
var count = 5; | var count = 5; | ||
var cut = Math.round((usableWidth / count) * 0. | var cut = Math.round((usableWidth / count) * 0.045); | ||
var top = []; | var top = []; | ||
var bottom = []; | var bottom = []; | ||
| 147번째 줄: | 166번째 줄: | ||
var i; | var i; | ||
cut = Math.max( | cut = Math.max(6, Math.min(10, cut)); | ||
for (i = 0; i <= count; i++) { | for (i = 0; i <= count; i++) { | ||
| 165번째 줄: | 184번째 줄: | ||
return [ | return [ | ||
[bottom[1] + 1, | [bottom[1] + 1, BUTTON_TOP], | ||
[bottom[4] - 1, | [bottom[4] - 1, BUTTON_TOP], | ||
[top[4] - 1, | [top[4] - 1, BUTTON_BOTTOM], | ||
[top[1] + 1, | [top[1] + 1, BUTTON_BOTTOM] | ||
]; | ]; | ||
} | } | ||
function readGuidePoints(portal, | function readGuidePoints(portal, mountRect) { | ||
var topNav = portal.querySelector('[data-component="category-nav"]'); | var topNav = portal.querySelector('[data-component="category-nav"]'); | ||
var topSvg = topNav && topNav.querySelector('.portal-category-svg'); | |||
var settings = topNav && topNav.querySelector( | var settings = topNav && topNav.querySelector( | ||
'.portal-cat-anchor[data-category-key="settings"] .portal-cat-edge' | '.portal-cat-anchor[data-category-key="settings"] .portal-cat-edge' | ||
| 183번째 줄: | 202번째 줄: | ||
var settingsPoints = settings ? parsePoints(settings.getAttribute('points')) : []; | var settingsPoints = settings ? parsePoints(settings.getAttribute('points')) : []; | ||
var corporationPoints = corporations ? parsePoints(corporations.getAttribute('points')) : []; | var corporationPoints = corporations ? parsePoints(corporations.getAttribute('points')) : []; | ||
var svgRect; | |||
var viewBox; | |||
var scaleX; | |||
function localX(svgX) { | |||
return Math.round( | |||
svgRect.left + | |||
(svgX - viewBox.x) * scaleX - | |||
mountRect.left | |||
); | |||
} | |||
/* | /* | ||
상단 | 상단 SVG의 실제 화면 좌표를 길라잡이 행의 로컬 좌표로 변환한다. | ||
프레임·2px 우물 간격이 추가되어도 설정–프로젝트–기업 및 공동체 | |||
경계와 정확히 일치한다. | |||
*/ | |||
if ( | |||
topSvg && | |||
settingsPoints.length === 4 && | |||
corporationPoints.length === 4 | |||
) { | |||
svgRect = topSvg.getBoundingClientRect(); | |||
viewBox = topSvg.viewBox && topSvg.viewBox.baseVal; | |||
if (viewBox && viewBox.width > 0 && svgRect.width > 0) { | |||
scaleX = svgRect.width / viewBox.width; | |||
return [ | |||
[localX(settingsPoints[3][0]), BUTTON_TOP], | |||
[localX(corporationPoints[2][0]), BUTTON_TOP], | |||
[localX(corporationPoints[1][0]), BUTTON_BOTTOM], | |||
[localX(settingsPoints[0][0]), BUTTON_BOTTOM] | |||
]; | |||
} | |||
} | } | ||
return fallbackGeometry(width); | return fallbackGeometry(Math.floor(mountRect.width)); | ||
} | |||
function frameBounds(points, width) { | |||
var xs = points.map(function (point) { return point[0]; }); | |||
var left = Math.floor(Math.min.apply(Math, xs) - GAP - FRAME); | |||
var right = Math.ceil(Math.max.apply(Math, xs) + GAP + FRAME); | |||
left = Math.max(0, left); | |||
right = Math.min(width, right); | |||
return { | |||
left: left, | |||
right: right, | |||
width: Math.max(0, right - left) | |||
}; | |||
} | |||
function appendOpenFrame(svg, bounds) { | |||
var group = createSvgElement('g'); | |||
var left = bounds.left; | |||
var right = bounds.right; | |||
var width = bounds.width; | |||
var surface = FRAME - 1; | |||
group.setAttribute('class', 'portal-guide-open-frame'); | |||
group.setAttribute('pointer-events', 'none'); | |||
/* #050505 최외곽 U */ | |||
group.appendChild(createRect( | |||
'portal-guide-frame-edge', | |||
left, 0, width, FRAME | |||
)); | |||
group.appendChild(createRect( | |||
'portal-guide-frame-edge', | |||
left, 0, FRAME, SHARED_BOTTOM_TOP | |||
)); | |||
group.appendChild(createRect( | |||
'portal-guide-frame-edge', | |||
right - FRAME, 0, FRAME, SHARED_BOTTOM_TOP | |||
)); | |||
/* #1d1d1d 프레임 면 */ | |||
group.appendChild(createRect( | |||
'portal-guide-frame-surface', | |||
left + 1, 1, width - 2, surface | |||
)); | |||
group.appendChild(createRect( | |||
'portal-guide-frame-surface', | |||
left + 1, 1, surface, SHARED_BOTTOM_TOP - 1 | |||
)); | |||
group.appendChild(createRect( | |||
'portal-guide-frame-surface', | |||
right - FRAME, 1, surface, SHARED_BOTTOM_TOP - 1 | |||
)); | |||
/* 우측 상단 광원과 좌측 그림자. 하단선은 의도적으로 없다. */ | |||
group.appendChild(createLine( | |||
'portal-guide-frame-inset portal-guide-frame-inset-top', | |||
[left + 1, 1], | |||
[right - 1, 1] | |||
)); | |||
group.appendChild(createLine( | |||
'portal-guide-frame-inset portal-guide-frame-inset-right', | |||
[right - 2, 1], | |||
[right - 2, SHARED_BOTTOM_TOP] | |||
)); | |||
group.appendChild(createLine( | |||
'portal-guide-frame-inset portal-guide-frame-inset-left', | |||
[left + 1, 1], | |||
[left + 1, SHARED_BOTTOM_TOP] | |||
)); | |||
svg.appendChild(group); | |||
} | } | ||
| 272번째 줄: | 381번째 줄: | ||
var topMount = portal.querySelector('[data-component="category-nav"]'); | var topMount = portal.querySelector('[data-component="category-nav"]'); | ||
var mountRect = mount.getBoundingClientRect(); | var mountRect = mount.getBoundingClientRect(); | ||
var width = Math.floor(mountRect.width); | var width = Math.floor(mountRect.width); | ||
if (!width) return false; | if (!width) return false; | ||
var points = readGuidePoints(portal, width | var points = readGuidePoints(portal, mountRect); | ||
var bounds = frameBounds(points, width); | |||
var surfacePoints = insetPolygon(points, OUTER_BORDER); | var surfacePoints = insetPolygon(points, OUTER_BORDER); | ||
var insetPoints = insetPolygon(points, INSET_OFFSET); | var insetPoints = insetPolygon(points, INSET_OFFSET); | ||
var center = getCentroid(surfacePoints); | var center = getCentroid(surfacePoints); | ||
var nav = document.createElement('nav'); | var nav = document.createElement('nav'); | ||
| 296번째 줄: | 403번째 줄: | ||
svg.setAttribute('class', 'portal-category-svg portal-guide-svg'); | svg.setAttribute('class', 'portal-category-svg portal-guide-svg'); | ||
svg.setAttribute('viewBox', '0 0 ' + width + ' ' + | svg.setAttribute('viewBox', '0 0 ' + width + ' ' + ROW_HEIGHT); | ||
svg.setAttribute('width', '100%'); | svg.setAttribute('width', '100%'); | ||
svg.setAttribute('height', '100%'); | svg.setAttribute('height', '100%'); | ||
svg.setAttribute('preserveAspectRatio', 'none'); | svg.setAttribute('preserveAspectRatio', 'none'); | ||
appendOpenFrame(svg, bounds); | |||
anchor.setAttribute('class', 'portal-cat-anchor portal-guide-anchor'); | anchor.setAttribute('class', 'portal-cat-anchor portal-guide-anchor'); | ||
| 313번째 줄: | 422번째 줄: | ||
text.setAttribute('class', 'portal-cat-label portal-guide-label'); | text.setAttribute('class', 'portal-cat-label portal-guide-label'); | ||
text.setAttribute('x', Math.round(center[0])); | text.setAttribute('x', Math.round(center[0])); | ||
text.setAttribute('y', Math.round( | text.setAttribute('y', Math.round((BUTTON_TOP + BUTTON_BOTTOM) / 2) + 1); | ||
text.setAttribute('font-size', '12'); | text.setAttribute('font-size', '12'); | ||
text.setAttribute('font-weight', '700'); | text.setAttribute('font-weight', '700'); | ||
| 413번째 줄: | 522번째 줄: | ||
window.BottomGuideNav = { | window.BottomGuideNav = { | ||
version: '20260712- | version: '20260712-exact-u-weld-001', | ||
render: function () { | render: function () { | ||
var portal = document.querySelector('.main-portal'); | var portal = document.querySelector('.main-portal'); | ||
2026년 7월 12일 (일) 10:15 판
/* =========================================
BottomGuideNav
대문 하단 「길라잡이」 SVG 버튼
========================================= */
/*
새 기능과 공개 이름에는 프로젝트 접두사를 사용하지 않는다.
상단 CategoryNav와 분리해 로드하며, 상단 SVG의 실제 좌표를 읽어
설정–프로젝트–기업 및 공동체의 합산 경계와 정확히 맞춘다.
*/
(function (window, document, mw) {
'use strict';
var SVG_NS = 'http://www.w3.org/2000/svg';
var XLINK_NS = 'http://www.w3.org/1999/xlink';
var COMPONENT = 'category-guide-nav';
var ROW_HEIGHT = 36;
var HEIGHT = 24;
var FRAME = 4;
var GAP = 2;
var BUTTON_TOP = FRAME + GAP;
var BUTTON_BOTTOM = BUTTON_TOP + HEIGHT;
var SHARED_BOTTOM_TOP = BUTTON_BOTTOM + GAP;
var OUTER_BORDER = 1;
var INSET_OFFSET = 1.5;
var retryTimer = 0;
var resizeTimer = 0;
var observer = null;
function createSvgElement(name) {
return document.createElementNS(SVG_NS, name);
}
function buildWikiPath(title) {
if (typeof window.buildWikiPath === 'function') {
return window.buildWikiPath(title);
}
return '/index.php/' + encodeURI(String(title || '').replace(/ /g, '_'));
}
function roundCoordinate(value) {
return Math.round(value * 100) / 100;
}
function pointsToString(points) {
return points.map(function (point) {
return roundCoordinate(point[0]) + ',' + roundCoordinate(point[1]);
}).join(' ');
}
function parsePoints(value) {
return String(value || '').trim().split(/\s+/).map(function (pair) {
var parts = pair.split(',');
return [Number(parts[0]), Number(parts[1])];
}).filter(function (point) {
return Number.isFinite(point[0]) && Number.isFinite(point[1]);
});
}
function getCentroid(points) {
var x = 0;
var y = 0;
points.forEach(function (point) {
x += point[0];
y += point[1];
});
return [x / points.length, y / points.length];
}
function intersectLines(lineA, lineB) {
var p = lineA.point;
var r = lineA.direction;
var q = lineB.point;
var s = lineB.direction;
var cross = r[0] * s[1] - r[1] * s[0];
if (Math.abs(cross) < 0.000001) {
return [p[0], p[1]];
}
var qp = [q[0] - p[0], q[1] - p[1]];
var t = (qp[0] * s[1] - qp[1] * s[0]) / cross;
return [p[0] + t * r[0], p[1] + t * r[1]];
}
function insetPolygon(points, distance) {
var center = getCentroid(points);
var lines = [];
var count = points.length;
var i;
for (i = 0; i < count; i++) {
var start = points[i];
var end = points[(i + 1) % count];
var dx = end[0] - start[0];
var dy = end[1] - start[1];
var length = Math.sqrt(dx * dx + dy * dy) || 1;
var normal = [-dy / length, dx / length];
var midpoint = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2];
var towardCenter = [center[0] - midpoint[0], center[1] - midpoint[1]];
if (normal[0] * towardCenter[0] + normal[1] * towardCenter[1] < 0) {
normal[0] *= -1;
normal[1] *= -1;
}
lines.push({
point: [
start[0] + normal[0] * distance,
start[1] + normal[1] * distance
],
direction: [dx, dy]
});
}
return lines.map(function (line, index) {
return intersectLines(lines[(index + count - 1) % count], line);
});
}
function createPolygon(className, points) {
var polygon = createSvgElement('polygon');
polygon.setAttribute('class', className);
polygon.setAttribute('points', pointsToString(points));
return polygon;
}
function createLine(className, start, end) {
var line = createSvgElement('line');
line.setAttribute('class', className);
line.setAttribute('x1', roundCoordinate(start[0]));
line.setAttribute('y1', roundCoordinate(start[1]));
line.setAttribute('x2', roundCoordinate(end[0]));
line.setAttribute('y2', roundCoordinate(end[1]));
line.setAttribute('vector-effect', 'non-scaling-stroke');
line.setAttribute('pointer-events', 'none');
return line;
}
function createRect(className, x, y, width, height) {
var rect = createSvgElement('rect');
rect.setAttribute('class', className);
rect.setAttribute('x', roundCoordinate(x));
rect.setAttribute('y', roundCoordinate(y));
rect.setAttribute('width', roundCoordinate(width));
rect.setAttribute('height', roundCoordinate(height));
rect.setAttribute('pointer-events', 'none');
return rect;
}
function fallbackGeometry(width) {
var usableWidth = Math.max(320, Math.floor(width || 0));
var count = 5;
var cut = Math.round((usableWidth / count) * 0.045);
var top = [];
var bottom = [];
var half = count / 2;
var i;
cut = Math.max(6, Math.min(10, cut));
for (i = 0; i <= count; i++) {
var x = Math.round((usableWidth * i) / count);
if (i === 0 || i === count || i === half) {
top.push(x);
bottom.push(x);
} else if (i < half) {
top.push(x - cut);
bottom.push(x);
} else {
top.push(x);
bottom.push(x - cut);
}
}
return [
[bottom[1] + 1, BUTTON_TOP],
[bottom[4] - 1, BUTTON_TOP],
[top[4] - 1, BUTTON_BOTTOM],
[top[1] + 1, BUTTON_BOTTOM]
];
}
function readGuidePoints(portal, mountRect) {
var topNav = portal.querySelector('[data-component="category-nav"]');
var topSvg = topNav && topNav.querySelector('.portal-category-svg');
var settings = topNav && topNav.querySelector(
'.portal-cat-anchor[data-category-key="settings"] .portal-cat-edge'
);
var corporations = topNav && topNav.querySelector(
'.portal-cat-anchor[data-category-key="corporations"] .portal-cat-edge'
);
var settingsPoints = settings ? parsePoints(settings.getAttribute('points')) : [];
var corporationPoints = corporations ? parsePoints(corporations.getAttribute('points')) : [];
var svgRect;
var viewBox;
var scaleX;
function localX(svgX) {
return Math.round(
svgRect.left +
(svgX - viewBox.x) * scaleX -
mountRect.left
);
}
/*
상단 SVG의 실제 화면 좌표를 길라잡이 행의 로컬 좌표로 변환한다.
프레임·2px 우물 간격이 추가되어도 설정–프로젝트–기업 및 공동체
경계와 정확히 일치한다.
*/
if (
topSvg &&
settingsPoints.length === 4 &&
corporationPoints.length === 4
) {
svgRect = topSvg.getBoundingClientRect();
viewBox = topSvg.viewBox && topSvg.viewBox.baseVal;
if (viewBox && viewBox.width > 0 && svgRect.width > 0) {
scaleX = svgRect.width / viewBox.width;
return [
[localX(settingsPoints[3][0]), BUTTON_TOP],
[localX(corporationPoints[2][0]), BUTTON_TOP],
[localX(corporationPoints[1][0]), BUTTON_BOTTOM],
[localX(settingsPoints[0][0]), BUTTON_BOTTOM]
];
}
}
return fallbackGeometry(Math.floor(mountRect.width));
}
function frameBounds(points, width) {
var xs = points.map(function (point) { return point[0]; });
var left = Math.floor(Math.min.apply(Math, xs) - GAP - FRAME);
var right = Math.ceil(Math.max.apply(Math, xs) + GAP + FRAME);
left = Math.max(0, left);
right = Math.min(width, right);
return {
left: left,
right: right,
width: Math.max(0, right - left)
};
}
function appendOpenFrame(svg, bounds) {
var group = createSvgElement('g');
var left = bounds.left;
var right = bounds.right;
var width = bounds.width;
var surface = FRAME - 1;
group.setAttribute('class', 'portal-guide-open-frame');
group.setAttribute('pointer-events', 'none');
/* #050505 최외곽 U */
group.appendChild(createRect(
'portal-guide-frame-edge',
left, 0, width, FRAME
));
group.appendChild(createRect(
'portal-guide-frame-edge',
left, 0, FRAME, SHARED_BOTTOM_TOP
));
group.appendChild(createRect(
'portal-guide-frame-edge',
right - FRAME, 0, FRAME, SHARED_BOTTOM_TOP
));
/* #1d1d1d 프레임 면 */
group.appendChild(createRect(
'portal-guide-frame-surface',
left + 1, 1, width - 2, surface
));
group.appendChild(createRect(
'portal-guide-frame-surface',
left + 1, 1, surface, SHARED_BOTTOM_TOP - 1
));
group.appendChild(createRect(
'portal-guide-frame-surface',
right - FRAME, 1, surface, SHARED_BOTTOM_TOP - 1
));
/* 우측 상단 광원과 좌측 그림자. 하단선은 의도적으로 없다. */
group.appendChild(createLine(
'portal-guide-frame-inset portal-guide-frame-inset-top',
[left + 1, 1],
[right - 1, 1]
));
group.appendChild(createLine(
'portal-guide-frame-inset portal-guide-frame-inset-right',
[right - 2, 1],
[right - 2, SHARED_BOTTOM_TOP]
));
group.appendChild(createLine(
'portal-guide-frame-inset portal-guide-frame-inset-left',
[left + 1, 1],
[left + 1, SHARED_BOTTOM_TOP]
));
svg.appendChild(group);
}
function ensureBodyPanel(portal) {
var panel = portal.querySelector('.main-body-panel');
var well = portal.querySelector('.main-body-well');
var topMount = portal.querySelector('[data-component="category-nav"]');
var manifesto = portal.querySelector('.main-manifesto');
if (!panel) {
panel = document.createElement('div');
panel.className = 'main-body-panel';
panel.setAttribute('data-main-body-panel-generated', '1');
if (topMount && topMount.parentNode) {
if (topMount.nextSibling) {
topMount.parentNode.insertBefore(panel, topMount.nextSibling);
} else {
topMount.parentNode.appendChild(panel);
}
} else {
portal.appendChild(panel);
}
}
if (!well) {
well = document.createElement('div');
well.className = 'main-body-well';
well.setAttribute('data-main-body-well-generated', '1');
panel.insertBefore(well, panel.firstChild);
} else if (well.parentNode !== panel) {
panel.insertBefore(well, panel.firstChild);
}
if (manifesto && manifesto.parentNode !== well) {
well.insertBefore(manifesto, well.firstChild);
}
return {
panel: panel,
well: well
};
}
function ensureMount(portal) {
var layout = ensureBodyPanel(portal);
var mount = portal.querySelector('[data-component="' + COMPONENT + '"]');
if (!mount) {
mount = document.createElement('div');
mount.setAttribute('data-component', COMPONENT);
mount.setAttribute('data-bottom-guide-generated', '1');
}
/*
길라잡이는 우물 안의 독립 버튼이 아니라
main-body-panel 하단 36px 프레임에 융합된 돌출부다.
*/
if (mount.parentNode !== layout.panel) {
layout.panel.appendChild(mount);
}
return mount;
}
function render(portal) {
var mount = ensureMount(portal);
var topMount = portal.querySelector('[data-component="category-nav"]');
var mountRect = mount.getBoundingClientRect();
var width = Math.floor(mountRect.width);
if (!width) return false;
var points = readGuidePoints(portal, mountRect);
var bounds = frameBounds(points, width);
var surfacePoints = insetPolygon(points, OUTER_BORDER);
var insetPoints = insetPolygon(points, INSET_OFFSET);
var center = getCentroid(surfacePoints);
var nav = document.createElement('nav');
var svg = createSvgElement('svg');
var anchor = createSvgElement('a');
var href = buildWikiPath('길라잡이');
var text = createSvgElement('text');
var outer = createPolygon('portal-cat-edge', points);
var surface = createPolygon('portal-cat-surface', surfacePoints);
var hit = createPolygon('portal-cat-hit', points);
nav.className = 'portal-category-nav portal-guide-nav is-panel-welded';
nav.setAttribute('aria-label', '길라잡이');
svg.setAttribute('class', 'portal-category-svg portal-guide-svg');
svg.setAttribute('viewBox', '0 0 ' + width + ' ' + ROW_HEIGHT);
svg.setAttribute('width', '100%');
svg.setAttribute('height', '100%');
svg.setAttribute('preserveAspectRatio', 'none');
appendOpenFrame(svg, bounds);
anchor.setAttribute('class', 'portal-cat-anchor portal-guide-anchor');
anchor.setAttribute('href', href);
anchor.setAttributeNS(XLINK_NS, 'xlink:href', href);
anchor.setAttribute('aria-label', '길라잡이');
anchor.setAttribute('data-category-key', 'guide');
outer.setAttribute('pointer-events', 'none');
surface.setAttribute('pointer-events', 'none');
hit.setAttribute('pointer-events', 'all');
text.setAttribute('class', 'portal-cat-label portal-guide-label');
text.setAttribute('x', Math.round(center[0]));
text.setAttribute('y', Math.round((BUTTON_TOP + BUTTON_BOTTOM) / 2) + 1);
text.setAttribute('font-size', '12');
text.setAttribute('font-weight', '700');
text.setAttribute('text-anchor', 'middle');
text.setAttribute('dominant-baseline', 'middle');
text.setAttribute('pointer-events', 'none');
text.textContent = '길라잡이';
anchor.appendChild(outer);
anchor.appendChild(surface);
anchor.appendChild(createLine(
'portal-cat-inset portal-cat-inset-top',
insetPoints[0],
insetPoints[1]
));
anchor.appendChild(createLine(
'portal-cat-inset portal-cat-inset-right',
insetPoints[1],
insetPoints[2]
));
anchor.appendChild(createLine(
'portal-cat-inset portal-cat-inset-bottom',
insetPoints[2],
insetPoints[3]
));
anchor.appendChild(createLine(
'portal-cat-inset portal-cat-inset-left',
insetPoints[3],
insetPoints[0]
));
anchor.appendChild(hit);
anchor.appendChild(text);
svg.appendChild(anchor);
nav.appendChild(svg);
mount.replaceChildren(nav);
mount.setAttribute('data-bottom-guide-rendered', '1');
mount.setAttribute('data-bottom-guide-width', String(width));
return true;
}
function findPortal(root) {
var scope = root && root.querySelector ? root : document;
return scope.matches && scope.matches('.main-portal')
? scope
: scope.querySelector('.main-portal');
}
function schedule(root, attempt) {
window.clearTimeout(retryTimer);
retryTimer = window.setTimeout(function () {
var portal = findPortal(root);
if (!portal) return;
if (!render(portal) && attempt < 20) {
schedule(root, attempt + 1);
return;
}
if (observer) observer.disconnect();
if (window.ResizeObserver) {
observer = new ResizeObserver(function () {
window.clearTimeout(resizeTimer);
resizeTimer = window.setTimeout(function () {
render(portal);
}, 60);
});
observer.observe(portal);
var topMount = portal.querySelector('[data-component="category-nav"]');
var guideMount = portal.querySelector('[data-component="' + COMPONENT + '"]');
if (topMount) observer.observe(topMount);
if (guideMount) observer.observe(guideMount);
}
}, attempt ? 60 : 0);
}
function boot(root) {
schedule(root || document, 0);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
boot(document);
}, { once: true });
} else {
boot(document);
}
if (mw && mw.hook) {
mw.hook('wikipage.content').add(function ($content) {
boot($content && $content[0] ? $content[0] : document);
});
}
window.BottomGuideNav = {
version: '20260712-exact-u-weld-001',
render: function () {
var portal = document.querySelector('.main-portal');
return portal ? render(portal) : false;
},
status: function () {
var portal = document.querySelector('.main-portal');
var mount = portal && portal.querySelector('[data-component="' + COMPONENT + '"]');
return {
loaded: true,
portal: !!portal,
mount: !!mount,
rendered: !!(mount && mount.querySelector('.portal-guide-svg')),
generated: !!(mount && mount.getAttribute('data-bottom-guide-generated')),
panel: !!(portal && portal.querySelector('.main-body-panel')),
well: !!(portal && portal.querySelector('.main-body-well')),
insidePanel: !!(mount && mount.parentNode && mount.parentNode.classList.contains('main-body-panel')),
welded: !!(mount && mount.querySelector('.portal-guide-nav.is-panel-welded')),
width: mount ? mount.getAttribute('data-bottom-guide-width') || '' : ''
};
}
};
}(window, document, window.mw));