(Install package: clbiwiki-mainpage-visual-pixel-fix-20260712 / js/CategoryNav.js) |
(Install package: clbiwiki-portal-frame-fixed / js/CategoryNav.js) 태그: 수동 되돌리기 |
||
| (같은 사용자의 중간 판 5개는 보이지 않습니다) | |||
| 1번째 줄: | 1번째 줄: | ||
/* ========================================= | /* ========================================= | ||
COASTLINE: BLACK ICE - CategoryNav | COASTLINE: BLACK ICE - CategoryNav | ||
대문 카테고리 | 대문 카테고리 canvas 픽셀 네비 생성기 | ||
========================================= */ | ========================================= */ | ||
(function (window, document, mw) { | |||
(function () { | |||
'use strict'; | 'use strict'; | ||
var ITEMS = [ | var ITEMS = [ | ||
{ key: 'era', label: '시대', title: '시대' }, | { key:'era', label:'시대', title:'시대', slant:'right' }, | ||
{ key: 'settings', label: '설정', title: '설정' }, | { key:'settings', label:'설정', title:'설정', slant:'right' }, | ||
{ key: 'project', label: '프로젝트', title: '프로젝트:소개' }, | { key:'project', label:'프로젝트', title:'프로젝트:소개', slant:'both', pillar:true }, | ||
{ key: 'corporations', label: '기업 및 공동체', title: '기업_및_공동체' }, | { key:'corporations', label:'기업 및 공동체', title:'기업_및_공동체', slant:'left' }, | ||
{ key: 'military', label: '군, 정치집단', title: '군_정치집단' } | { key:'military', label:'군, 정치집단', title:'군_정치집단', slant:'left' } | ||
]; | ]; | ||
var renderTimer = 0; | |||
function api() { | |||
return window.CLBI && window.CLBI.frame; | |||
} | |||
function buildPath(title) { | function buildPath(title) { | ||
if (typeof window.buildWikiPath === 'function') | if (typeof window.buildWikiPath === 'function') return window.buildWikiPath(title); | ||
return '/index.php/' + encodeURI(String(title || '').replace(/ /g, '_')); | return '/index.php/' + encodeURI(String(title || '').replace(/ /g, '_')); | ||
} | } | ||
function | function clearNode(node) { | ||
while (node && node.firstChild) node.removeChild(node.firstChild); | |||
} | } | ||
function | function calculateLayout(width) { | ||
var | var frame = api(); | ||
var f = frame.FRAME; | |||
var d = frame.DIMENSIONS; | |||
var contentX = d.WELL_TOP; | |||
var contentWidth = Math.max(ITEMS.length * 8, width - contentX * 2); | |||
var btnW = Math.max(4, Math.floor((contentWidth - f.GAP * 4) / ITEMS.length)); | |||
var segments = []; | |||
var xoff = contentX; | |||
]; | var i; | ||
for ( | for (i = 0; i < ITEMS.length; i++) { | ||
segments.push({ | |||
index:i, | |||
key:ITEMS[i].key, | |||
label:ITEMS[i].label, | |||
slant:ITEMS[i].slant, | |||
x:xoff, | |||
y:d.BUTTON_EDGE_TOP, | |||
btnW:btnW, | |||
pixelWidth:btnW + 2, | |||
pixelHeight:d.BUTTON_TOTAL | |||
}); | |||
xoff += btnW + f.GAP; | |||
} | } | ||
return { | |||
width:width, | |||
} | height:d.ROW_HEIGHT, | ||
contentX:contentX, | |||
contentWidth:contentWidth, | |||
buttonWidth:btnW, | |||
usedRight:xoff - f.GAP, | |||
segments:segments | |||
}; | |||
} | } | ||
function | function paintProjectPillar(image, width, height, segment) { | ||
var | var frame = api(); | ||
var | var d = frame.DIMENSIONS; | ||
var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE); | |||
var | var leftTop = segment.x - topStep; | ||
var | var rightTop = segment.x + segment.btnW + topStep; | ||
var | var y; | ||
var | var x; | ||
for (y = 0; y < height; y++) { | |||
var step = Math.floor(y / frame.FRAME.SLANT_RISE); | |||
var left = leftTop + step; | |||
var right = rightTop - step; | |||
for (x = left; x <= right; x++) frame.setPixel(image, width, height, x, y, 'S'); | |||
frame.setPixel(image, width, height, left, y, 'D'); | |||
frame.setPixel(image, width, height, right, y, 'H'); | |||
} | } | ||
return { | return { | ||
leftTop:leftTop, | |||
rightTop:rightTop, | |||
leftAtButton:segment.x, | |||
rightAtButton:segment.x + segment.btnW | |||
}; | }; | ||
} | } | ||
function | function labelCenter(segment) { | ||
return | var frame = api(); | ||
var ys = Math.floor(frame.FRAME.BUTTON_FACE / 2); | |||
var mask = frame.buttonMask(segment.btnW, ys, segment.slant, 0); | |||
return segment.x + Math.round((mask.left + mask.right) / 2); | |||
} | } | ||
function | function draw(canvas, layout, hoverIndex) { | ||
var frame = api(); | |||
var ctx = canvas.getContext('2d'); | |||
var image = frame.createImage(ctx, layout.width, layout.height, 'BG'); | |||
var projectGeometry = null; | |||
frame.paintStandardFrame(image, layout.width, layout.height); | |||
layout.segments.forEach(function (segment) { | |||
var grid = frame.buildButtonPixels( | |||
} | segment.btnW, | ||
frame.FRAME.BUTTON_FACE, | |||
segment.slant, | |||
0 | |||
); | |||
frame.paintGrid( | |||
image, | |||
layout.width, | |||
layout.height, | |||
grid, | |||
segment.x, | |||
segment.y, | |||
true | |||
); | |||
}); | |||
projectGeometry = paintProjectPillar( | |||
image, | |||
layout.width, | |||
layout.height, | |||
layout.segments[2] | |||
); | |||
ctx.putImageData(image, 0, 0); | |||
layout.segments.forEach(function (segment, index) { | |||
var center = index === 2 | |||
? Math.round((projectGeometry.leftAtButton + projectGeometry.rightAtButton) / 2) | |||
: labelCenter(segment); | |||
frame.drawLabel( | |||
ctx, | |||
segment.label, | |||
center, | |||
Math.round((frame.DIMENSIONS.BUTTON_SURFACE_TOP + frame.DIMENSIONS.BUTTON_SURFACE_BOTTOM) / 2) + 1, | |||
hoverIndex === index && index !== 2, | |||
layout.width <= 820 ? 11 : 12 | |||
); | |||
}); | |||
canvas.__projectGeometry = projectGeometry; | |||
} | } | ||
function | function publishSeams(layout) { | ||
var | var frame = api(); | ||
var | var d = frame.DIMENSIONS; | ||
var settings = layout.segments[1]; | |||
var project = layout.segments[2]; | |||
var corporations = layout.segments[3]; | |||
var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE); | |||
var settingsLeftOriginX = settings.x - topStep; | |||
var corpRightOriginX = corporations.x + corporations.btnW + topStep; | |||
var projectLeftOriginX = project.x - topStep; | |||
var projectRightOriginX = project.x + project.btnW + topStep; | |||
var seamStep = Math.floor(d.BOTTOM_START_ABS_Y / frame.FRAME.SLANT_RISE); | |||
frame.setSeams({ | |||
width:layout.width, | |||
rowHeight:d.ROW_HEIGHT, | |||
buttonTop:d.BUTTON_EDGE_TOP, | |||
buttonBottom:d.BUTTON_EDGE_BOTTOM, | |||
overlap:d.OVERLAP, | |||
bottomStartAbsY:d.BOTTOM_START_ABS_Y, | |||
phase:d.BOTTOM_START_ABS_Y % frame.FRAME.SLANT_RISE, | |||
settingsLeftOriginX:settingsLeftOriginX, | |||
settingsLeftTopX:settings.x, | |||
settingsLeftSeamX:settingsLeftOriginX + seamStep, | |||
settingsLeftDir:1, | |||
corpRightOriginX:corpRightOriginX, | |||
corpRightTopX:corporations.x + corporations.btnW, | |||
corpRightSeamX:corpRightOriginX - seamStep, | |||
corpRightDir:-1, | |||
projectLeftOriginX:projectLeftOriginX, | |||
projectRightOriginX:projectRightOriginX, | |||
projectLeftTopX:project.x, | |||
projectRightTopX:project.x + project.btnW, | |||
projectLeftDir:1, | |||
projectRightDir:-1, | |||
buttonWidth:layout.buttonWidth, | |||
segments:layout.segments.map(function (segment) { | |||
return { | |||
key:segment.key, | |||
x:segment.x, | |||
y:segment.y, | |||
btnW:segment.btnW, | |||
pixelWidth:segment.pixelWidth, | |||
slant:segment.slant | |||
}; | |||
}), | |||
shellScale:frame.getShellScale() | |||
}); | }); | ||
} | } | ||
function | function createHit(item, index, segment, nav, redraw) { | ||
var | var hit; | ||
if (item.pillar) { | |||
hit = document.createElement('span'); | |||
hit.setAttribute('aria-label', item.label); | |||
hit.setAttribute('role', 'presentation'); | |||
hit.className = 'portal-frame-hit portal-category-pillar-hit'; | |||
} else { | |||
hit = document.createElement('a'); | |||
hit.href = buildPath(item.title); | |||
hit.setAttribute('aria-label', item.label); | |||
hit.className = 'portal-frame-hit portal-cat-anchor'; | |||
hit.setAttribute('data-category-key', item.key); | |||
hit.addEventListener('mouseenter', function () { | |||
nav.__frameHoverIndex = index; | |||
redraw(); | |||
}); | |||
hit.addEventListener('mouseleave', function () { | |||
if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1; | |||
redraw(); | |||
}); | |||
hit.addEventListener('focus', function () { | |||
nav.__frameHoverIndex = index; | |||
redraw(); | |||
}); | |||
hit.addEventListener('blur', function () { | |||
if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1; | |||
redraw(); | |||
}); | |||
} | } | ||
hit.style.left = segment.x + 'px'; | |||
hit.style.top = segment.y + 'px'; | |||
hit.style.width = segment.pixelWidth + 'px'; | |||
hit.style.height = segment.pixelHeight + 'px'; | |||
return | hit.appendChild(document.createElement('span')); | ||
hit.lastChild.className = 'portal-frame-sr-label'; | |||
hit.lastChild.textContent = item.label; | |||
return hit; | |||
} | } | ||
function | function renderMount(mount) { | ||
var | var frame = api(); | ||
var | var width; | ||
var | var layout; | ||
var | var nav; | ||
var inner; | |||
var canvas; | |||
var hitLayer; | |||
var redraw; | |||
if (!mount || !frame) return false; | |||
frame.setShellScale(frame.getShellScale()); | |||
width = frame.readRenderWidth(mount); | |||
if (!width) { | |||
if (!mount.__categoryNavRetry) { | |||
mount.__categoryNavRetry = window.setTimeout(function () { | |||
mount.__categoryNavRetry = 0; | |||
renderMount(mount); | |||
}, 60); | |||
} | } | ||
return false; | |||
} | |||
layout = calculateLayout(width); | |||
if ( | |||
mount.getAttribute('data-category-nav-width') === String(width) && | |||
mount.getAttribute('data-category-nav-scale') === String(frame.getShellScale()) && | |||
mount.querySelector('.portal-category-canvas') | |||
) { | |||
publishSeams(layout); | |||
return true; | |||
} | } | ||
nav = document.createElement('nav'); | |||
nav.className = 'portal-category-nav portal-frame-canvas-nav'; | |||
nav.setAttribute('aria-label', 'Main categories'); | |||
nav.__frameHoverIndex = -1; | |||
inner = document.createElement('div'); | |||
inner.className = 'portal-frame-counter-inner portal-category-counter-inner'; | |||
inner.style.width = width + 'px'; | |||
canvas = frame.createCanvas('portal-frame-canvas portal-category-canvas', width, layout.height); | |||
canvas.setAttribute('data-frame-width', String(width)); | |||
canvas.setAttribute('data-frame-height', String(layout.height)); | |||
hitLayer = document.createElement('div'); | |||
hitLayer.className = 'portal-frame-hit-layer'; | |||
hitLayer.style.width = width + 'px'; | |||
hitLayer.style.height = layout.height + 'px'; | |||
redraw = function () { | |||
draw(canvas, layout, nav.__frameHoverIndex); | |||
}; | |||
ITEMS.forEach(function (item, index) { | ITEMS.forEach(function (item, index) { | ||
hitLayer.appendChild(createHit(item, index, layout.segments[index], nav, redraw)); | |||
}); | }); | ||
inner.appendChild(canvas); | |||
inner.appendChild(hitLayer); | |||
nav.appendChild(inner); | |||
clearNode(mount); | clearNode(mount); | ||
mount.appendChild(nav); | mount.appendChild(nav); | ||
frame.applyCounterScale([mount, nav], inner, layout.height); | |||
redraw(); | |||
publishSeams(layout); | |||
mount.setAttribute('data-category-nav-rendered', 'canvas'); | |||
mount.setAttribute('data-category-nav-width', String(width)); | |||
mount.setAttribute('data-category-nav-scale', String(frame.getShellScale())); | |||
if (window.ResizeObserver && !mount.__categoryNavObserver) { | |||
mount.__categoryNavObserver = new window.ResizeObserver(function () { | |||
mount.__categoryNavObserver = | schedule(mount); | ||
}); | }); | ||
mount.__categoryNavObserver.observe(mount); | |||
} | } | ||
return true; | |||
} | } | ||
function | function renderAll(root) { | ||
var scope = root | var scope = root && root.querySelectorAll ? root : document; | ||
var mounts = scope. | var mounts = []; | ||
var rendered = 0; | |||
Array.prototype.forEach.call(mounts | if (scope.matches && scope.matches('[data-component="category-nav"]')) mounts.push(scope); | ||
Array.prototype.forEach.call(scope.querySelectorAll('[data-component="category-nav"]'), function (mount) { | |||
if (mounts.indexOf(mount) === -1) mounts.push(mount); | |||
}); | |||
mounts.forEach(function (mount) { | |||
if (renderMount(mount)) rendered += 1; | |||
}); | }); | ||
return rendered; | |||
} | } | ||
window. | function schedule(root) { | ||
window.clearTimeout(renderTimer); | |||
renderTimer = window.setTimeout(function () { | |||
renderTimer = 0; | |||
} | renderAll(root || document); | ||
}, 0); | |||
} | |||
if (document.readyState === 'loading') { | if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', function () { | document.addEventListener('DOMContentLoaded', function () { renderAll(document); }); | ||
} else { | } else { | ||
renderAll(document); | |||
} | } | ||
if ( | if (api()) api().onScale(function () { schedule(document); }); | ||
mw.hook('wikipage.content').add(function ( | if (mw && mw.hook) { | ||
mw.hook('wikipage.content').add(function (content) { | |||
schedule(content && content[0] ? content[0] : document); | |||
}); | }); | ||
} | } | ||
}()); | window.addEventListener('resize', function () { schedule(document); }); | ||
window.CategoryNav = { | |||
version:'20260717-canvas-pixel-001', | |||
init:renderAll, | |||
renderAll:renderAll, | |||
renderMount:renderMount | |||
}; | |||
window.CLBI = window.CLBI || {}; | |||
window.CLBI.categoryNav = { | |||
init:renderAll, | |||
renderMount:renderMount | |||
}; | |||
}(window, document, window.mw)); | |||
2026년 7월 17일 (금) 17:49 기준 최신판
/* =========================================
COASTLINE: BLACK ICE - CategoryNav
대문 카테고리 canvas 픽셀 네비 생성기
========================================= */
(function (window, document, mw) {
'use strict';
var ITEMS = [
{ key:'era', label:'시대', title:'시대', slant:'right' },
{ key:'settings', label:'설정', title:'설정', slant:'right' },
{ key:'project', label:'프로젝트', title:'프로젝트:소개', slant:'both', pillar:true },
{ key:'corporations', label:'기업 및 공동체', title:'기업_및_공동체', slant:'left' },
{ key:'military', label:'군, 정치집단', title:'군_정치집단', slant:'left' }
];
var renderTimer = 0;
function api() {
return window.CLBI && window.CLBI.frame;
}
function buildPath(title) {
if (typeof window.buildWikiPath === 'function') return window.buildWikiPath(title);
return '/index.php/' + encodeURI(String(title || '').replace(/ /g, '_'));
}
function clearNode(node) {
while (node && node.firstChild) node.removeChild(node.firstChild);
}
function calculateLayout(width) {
var frame = api();
var f = frame.FRAME;
var d = frame.DIMENSIONS;
var contentX = d.WELL_TOP;
var contentWidth = Math.max(ITEMS.length * 8, width - contentX * 2);
var btnW = Math.max(4, Math.floor((contentWidth - f.GAP * 4) / ITEMS.length));
var segments = [];
var xoff = contentX;
var i;
for (i = 0; i < ITEMS.length; i++) {
segments.push({
index:i,
key:ITEMS[i].key,
label:ITEMS[i].label,
slant:ITEMS[i].slant,
x:xoff,
y:d.BUTTON_EDGE_TOP,
btnW:btnW,
pixelWidth:btnW + 2,
pixelHeight:d.BUTTON_TOTAL
});
xoff += btnW + f.GAP;
}
return {
width:width,
height:d.ROW_HEIGHT,
contentX:contentX,
contentWidth:contentWidth,
buttonWidth:btnW,
usedRight:xoff - f.GAP,
segments:segments
};
}
function paintProjectPillar(image, width, height, segment) {
var frame = api();
var d = frame.DIMENSIONS;
var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE);
var leftTop = segment.x - topStep;
var rightTop = segment.x + segment.btnW + topStep;
var y;
var x;
for (y = 0; y < height; y++) {
var step = Math.floor(y / frame.FRAME.SLANT_RISE);
var left = leftTop + step;
var right = rightTop - step;
for (x = left; x <= right; x++) frame.setPixel(image, width, height, x, y, 'S');
frame.setPixel(image, width, height, left, y, 'D');
frame.setPixel(image, width, height, right, y, 'H');
}
return {
leftTop:leftTop,
rightTop:rightTop,
leftAtButton:segment.x,
rightAtButton:segment.x + segment.btnW
};
}
function labelCenter(segment) {
var frame = api();
var ys = Math.floor(frame.FRAME.BUTTON_FACE / 2);
var mask = frame.buttonMask(segment.btnW, ys, segment.slant, 0);
return segment.x + Math.round((mask.left + mask.right) / 2);
}
function draw(canvas, layout, hoverIndex) {
var frame = api();
var ctx = canvas.getContext('2d');
var image = frame.createImage(ctx, layout.width, layout.height, 'BG');
var projectGeometry = null;
frame.paintStandardFrame(image, layout.width, layout.height);
layout.segments.forEach(function (segment) {
var grid = frame.buildButtonPixels(
segment.btnW,
frame.FRAME.BUTTON_FACE,
segment.slant,
0
);
frame.paintGrid(
image,
layout.width,
layout.height,
grid,
segment.x,
segment.y,
true
);
});
projectGeometry = paintProjectPillar(
image,
layout.width,
layout.height,
layout.segments[2]
);
ctx.putImageData(image, 0, 0);
layout.segments.forEach(function (segment, index) {
var center = index === 2
? Math.round((projectGeometry.leftAtButton + projectGeometry.rightAtButton) / 2)
: labelCenter(segment);
frame.drawLabel(
ctx,
segment.label,
center,
Math.round((frame.DIMENSIONS.BUTTON_SURFACE_TOP + frame.DIMENSIONS.BUTTON_SURFACE_BOTTOM) / 2) + 1,
hoverIndex === index && index !== 2,
layout.width <= 820 ? 11 : 12
);
});
canvas.__projectGeometry = projectGeometry;
}
function publishSeams(layout) {
var frame = api();
var d = frame.DIMENSIONS;
var settings = layout.segments[1];
var project = layout.segments[2];
var corporations = layout.segments[3];
var topStep = Math.floor(d.BUTTON_EDGE_TOP / frame.FRAME.SLANT_RISE);
var settingsLeftOriginX = settings.x - topStep;
var corpRightOriginX = corporations.x + corporations.btnW + topStep;
var projectLeftOriginX = project.x - topStep;
var projectRightOriginX = project.x + project.btnW + topStep;
var seamStep = Math.floor(d.BOTTOM_START_ABS_Y / frame.FRAME.SLANT_RISE);
frame.setSeams({
width:layout.width,
rowHeight:d.ROW_HEIGHT,
buttonTop:d.BUTTON_EDGE_TOP,
buttonBottom:d.BUTTON_EDGE_BOTTOM,
overlap:d.OVERLAP,
bottomStartAbsY:d.BOTTOM_START_ABS_Y,
phase:d.BOTTOM_START_ABS_Y % frame.FRAME.SLANT_RISE,
settingsLeftOriginX:settingsLeftOriginX,
settingsLeftTopX:settings.x,
settingsLeftSeamX:settingsLeftOriginX + seamStep,
settingsLeftDir:1,
corpRightOriginX:corpRightOriginX,
corpRightTopX:corporations.x + corporations.btnW,
corpRightSeamX:corpRightOriginX - seamStep,
corpRightDir:-1,
projectLeftOriginX:projectLeftOriginX,
projectRightOriginX:projectRightOriginX,
projectLeftTopX:project.x,
projectRightTopX:project.x + project.btnW,
projectLeftDir:1,
projectRightDir:-1,
buttonWidth:layout.buttonWidth,
segments:layout.segments.map(function (segment) {
return {
key:segment.key,
x:segment.x,
y:segment.y,
btnW:segment.btnW,
pixelWidth:segment.pixelWidth,
slant:segment.slant
};
}),
shellScale:frame.getShellScale()
});
}
function createHit(item, index, segment, nav, redraw) {
var hit;
if (item.pillar) {
hit = document.createElement('span');
hit.setAttribute('aria-label', item.label);
hit.setAttribute('role', 'presentation');
hit.className = 'portal-frame-hit portal-category-pillar-hit';
} else {
hit = document.createElement('a');
hit.href = buildPath(item.title);
hit.setAttribute('aria-label', item.label);
hit.className = 'portal-frame-hit portal-cat-anchor';
hit.setAttribute('data-category-key', item.key);
hit.addEventListener('mouseenter', function () {
nav.__frameHoverIndex = index;
redraw();
});
hit.addEventListener('mouseleave', function () {
if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;
redraw();
});
hit.addEventListener('focus', function () {
nav.__frameHoverIndex = index;
redraw();
});
hit.addEventListener('blur', function () {
if (nav.__frameHoverIndex === index) nav.__frameHoverIndex = -1;
redraw();
});
}
hit.style.left = segment.x + 'px';
hit.style.top = segment.y + 'px';
hit.style.width = segment.pixelWidth + 'px';
hit.style.height = segment.pixelHeight + 'px';
hit.appendChild(document.createElement('span'));
hit.lastChild.className = 'portal-frame-sr-label';
hit.lastChild.textContent = item.label;
return hit;
}
function renderMount(mount) {
var frame = api();
var width;
var layout;
var nav;
var inner;
var canvas;
var hitLayer;
var redraw;
if (!mount || !frame) return false;
frame.setShellScale(frame.getShellScale());
width = frame.readRenderWidth(mount);
if (!width) {
if (!mount.__categoryNavRetry) {
mount.__categoryNavRetry = window.setTimeout(function () {
mount.__categoryNavRetry = 0;
renderMount(mount);
}, 60);
}
return false;
}
layout = calculateLayout(width);
if (
mount.getAttribute('data-category-nav-width') === String(width) &&
mount.getAttribute('data-category-nav-scale') === String(frame.getShellScale()) &&
mount.querySelector('.portal-category-canvas')
) {
publishSeams(layout);
return true;
}
nav = document.createElement('nav');
nav.className = 'portal-category-nav portal-frame-canvas-nav';
nav.setAttribute('aria-label', 'Main categories');
nav.__frameHoverIndex = -1;
inner = document.createElement('div');
inner.className = 'portal-frame-counter-inner portal-category-counter-inner';
inner.style.width = width + 'px';
canvas = frame.createCanvas('portal-frame-canvas portal-category-canvas', width, layout.height);
canvas.setAttribute('data-frame-width', String(width));
canvas.setAttribute('data-frame-height', String(layout.height));
hitLayer = document.createElement('div');
hitLayer.className = 'portal-frame-hit-layer';
hitLayer.style.width = width + 'px';
hitLayer.style.height = layout.height + 'px';
redraw = function () {
draw(canvas, layout, nav.__frameHoverIndex);
};
ITEMS.forEach(function (item, index) {
hitLayer.appendChild(createHit(item, index, layout.segments[index], nav, redraw));
});
inner.appendChild(canvas);
inner.appendChild(hitLayer);
nav.appendChild(inner);
clearNode(mount);
mount.appendChild(nav);
frame.applyCounterScale([mount, nav], inner, layout.height);
redraw();
publishSeams(layout);
mount.setAttribute('data-category-nav-rendered', 'canvas');
mount.setAttribute('data-category-nav-width', String(width));
mount.setAttribute('data-category-nav-scale', String(frame.getShellScale()));
if (window.ResizeObserver && !mount.__categoryNavObserver) {
mount.__categoryNavObserver = new window.ResizeObserver(function () {
schedule(mount);
});
mount.__categoryNavObserver.observe(mount);
}
return true;
}
function renderAll(root) {
var scope = root && root.querySelectorAll ? root : document;
var mounts = [];
var rendered = 0;
if (scope.matches && scope.matches('[data-component="category-nav"]')) mounts.push(scope);
Array.prototype.forEach.call(scope.querySelectorAll('[data-component="category-nav"]'), function (mount) {
if (mounts.indexOf(mount) === -1) mounts.push(mount);
});
mounts.forEach(function (mount) {
if (renderMount(mount)) rendered += 1;
});
return rendered;
}
function schedule(root) {
window.clearTimeout(renderTimer);
renderTimer = window.setTimeout(function () {
renderTimer = 0;
renderAll(root || document);
}, 0);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { renderAll(document); });
} else {
renderAll(document);
}
if (api()) api().onScale(function () { schedule(document); });
if (mw && mw.hook) {
mw.hook('wikipage.content').add(function (content) {
schedule(content && content[0] ? content[0] : document);
});
}
window.addEventListener('resize', function () { schedule(document); });
window.CategoryNav = {
version:'20260717-canvas-pixel-001',
init:renderAll,
renderAll:renderAll,
renderMount:renderMount
};
window.CLBI = window.CLBI || {};
window.CLBI.categoryNav = {
init:renderAll,
renderMount:renderMount
};
}(window, document, window.mw));