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