(Install package: clbiwiki-mainpage-manifesto-divider-true-growth-20260713 / js/ManifestoIntro.js) |
(Install package: clbiwiki-mainpage-manifesto-integer-grid-fix-20260713 / js/ManifestoIntro.js) |
||
| 8번째 줄: | 8번째 줄: | ||
실제 HTML 텍스트를 유지한 채 제목, 구분선, 본문 행을 순서대로 연다. | 실제 HTML 텍스트를 유지한 채 제목, 구분선, 본문 행을 순서대로 연다. | ||
이미지, canvas, 글자 래스터화는 사용하지 않는다. | 이미지, canvas, 글자 래스터화는 사용하지 않는다. | ||
픽셀 폰트 글리프에는 transform을 적용하지 않고 정수 CSS px만 사용한다. | |||
*/ | */ | ||
| 13번째 줄: | 14번째 줄: | ||
'use strict'; | 'use strict'; | ||
var VERSION = '20260713-procedural-004-integer-grid'; | |||
var PORTAL_SELECTOR = '.main-portal'; | var PORTAL_SELECTOR = '.main-portal'; | ||
var TITLE_SELECTOR = '.main-manifesto-title'; | var TITLE_SELECTOR = '.main-manifesto-title'; | ||
| 25번째 줄: | 27번째 줄: | ||
var LINE_DURATION = 230; | var LINE_DURATION = 230; | ||
var LINE_HEIGHT = 26; | var LINE_HEIGHT = 26; | ||
var INNER_WIDTH = 660; | |||
var COPY_WIDTH = 620; | |||
var portalStates = typeof WeakMap === 'function' ? new WeakMap() : null; | var portalStates = typeof WeakMap === 'function' ? new WeakMap() : null; | ||
var observer = null; | var observer = null; | ||
var resizeTimer = 0; | |||
function findPortal(root) { | function findPortal(root) { | ||
| 76번째 줄: | 81번째 줄: | ||
return Promise.race([load, timeout]); | return Promise.race([load, timeout]); | ||
} | |||
function snapManifestoToIntegerGrid(portal) { | |||
var manifesto = portal.querySelector('.main-manifesto'); | |||
var inner = portal.querySelector('.main-manifesto-inner'); | |||
var copy = portal.querySelector(COPY_SELECTOR); | |||
var available; | |||
var left; | |||
if (!manifesto || !inner || !copy) return; | |||
available = Math.floor(manifesto.clientWidth || manifesto.getBoundingClientRect().width || 0); | |||
left = available > INNER_WIDTH ? Math.round((available - INNER_WIDTH) / 2) : 0; | |||
manifesto.style.setProperty('justify-content', 'flex-start'); | |||
inner.style.setProperty('margin', '0', 'important'); | |||
inner.style.setProperty('margin-left', left + 'px', 'important'); | |||
inner.style.setProperty('transform', 'none', 'important'); | |||
copy.style.setProperty('margin', '0', 'important'); | |||
copy.style.setProperty('margin-left', Math.round((INNER_WIDTH - COPY_WIDTH) / 2) + 'px', 'important'); | |||
copy.style.setProperty('transform', 'none', 'important'); | |||
} | } | ||
| 108번째 줄: | 135번째 줄: | ||
var top = Math.round(token.getBoundingClientRect().top); | var top = Math.round(token.getBoundingClientRect().top); | ||
if (!current || | if (!current || current.top !== top) { | ||
current = { top: top, words: [] }; | current = { top: top, words: [] }; | ||
rows.push(current); | rows.push(current); | ||
| 170번째 줄: | 197번째 줄: | ||
} | } | ||
}); | }); | ||
} | |||
function ensureTitleText(title) { | |||
var existing = title.querySelector(':scope > .manifesto-title-text'); | |||
var divider = title.querySelector(':scope > .manifesto-divider'); | |||
var wrapper; | |||
var nodes; | |||
if (existing) return existing; | |||
wrapper = document.createElement('span'); | |||
wrapper.className = 'manifesto-title-text'; | |||
nodes = Array.prototype.slice.call(title.childNodes); | |||
nodes.forEach(function (node) { | |||
if (node !== divider) wrapper.appendChild(node); | |||
}); | |||
if (divider) { | |||
title.insertBefore(wrapper, divider); | |||
} else { | |||
title.appendChild(wrapper); | |||
} | |||
return wrapper; | |||
} | } | ||
function ensureDivider(title) { | function ensureDivider(title) { | ||
var divider = title.querySelector('.manifesto-divider'); | var divider = title.querySelector(':scope > .manifesto-divider'); | ||
var left; | |||
var right; | |||
if (!divider) { | if (!divider) { | ||
| 182번째 줄: | 236번째 줄: | ||
} | } | ||
return divider; | left = divider.querySelector('.manifesto-divider-left'); | ||
right = divider.querySelector('.manifesto-divider-right'); | |||
if (!left || !right) { | |||
divider.textContent = ''; | |||
left = document.createElement('span'); | |||
right = document.createElement('span'); | |||
left.className = 'manifesto-divider-half manifesto-divider-left'; | |||
right.className = 'manifesto-divider-half manifesto-divider-right'; | |||
divider.appendChild(left); | |||
divider.appendChild(right); | |||
} | |||
return { | |||
root: divider, | |||
left: left, | |||
right: right | |||
}; | |||
} | } | ||
function applyInitialFrame(portal, | function applyInitialFrame(portal, titleText, copy, divider, lines) { | ||
portal.classList.remove('manifesto-intro-complete'); | portal.classList.remove('manifesto-intro-complete'); | ||
portal.classList.add('manifesto-intro-prepared'); | portal.classList.add('manifesto-intro-prepared'); | ||
document.documentElement.classList.add('manifesto-intro-active'); | document.documentElement.classList.add('manifesto-intro-active'); | ||
titleText.style.opacity = '0'; | |||
divider | divider.left.style.width = '0px'; | ||
divider.right.style.width = '0px'; | |||
divider.style. | |||
lines.forEach(function (line) { | lines.forEach(function (line) { | ||
| 200번째 줄: | 270번째 줄: | ||
line.style.height = '0px'; | line.style.height = '0px'; | ||
line.style.maxHeight = '0px'; | line.style.maxHeight = '0px'; | ||
line.style.overflow = 'hidden'; | line.style.overflow = 'hidden'; | ||
line.style.clipPath = ' | line.style.opacity = '1'; | ||
line.style.clipPath = 'none'; | |||
if (inner) { | if (inner) { | ||
inner.style.opacity = ' | inner.style.opacity = '1'; | ||
inner.style.transform = ' | inner.style.transform = 'none'; | ||
} | } | ||
}); | }); | ||
| 212번째 줄: | 282번째 줄: | ||
copy.style.visibility = 'visible'; | copy.style.visibility = 'visible'; | ||
portal.getBoundingClientRect(); | portal.getBoundingClientRect(); | ||
lines.forEach(function (line) { line.getBoundingClientRect(); }); | lines.forEach(function (line) { line.getBoundingClientRect(); }); | ||
| 218번째 줄: | 287번째 줄: | ||
} | } | ||
function | function easeOutCubic(value) { | ||
var inverse = 1 - value; | |||
return 1 - (inverse * inverse * inverse); | |||
} | |||
function animateFrames(delay, duration, update, complete) { | |||
return new Promise(function (resolve) { | |||
window.setTimeout(function () { | |||
var start = window.performance && performance.now ? performance.now() : Date.now(); | |||
function frame(now) { | |||
var current = typeof now === 'number' ? now : Date.now(); | |||
var progress = Math.max(0, Math.min(1, (current - start) / duration)); | |||
update(easeOutCubic(progress), progress); | |||
if (progress < 1) { | |||
window.requestAnimationFrame(frame); | |||
} else { | |||
if (complete) complete(); | |||
resolve(); | |||
} | } | ||
} | |||
window.requestAnimationFrame(frame); | |||
}, delay); | |||
}); | }); | ||
} | } | ||
function animateTitle( | function animateTitle(titleText) { | ||
return animateFrames(TITLE_DELAY, TITLE_DURATION, function (eased) { | |||
titleText.style.opacity = String(Math.round(eased * 1000) / 1000); | |||
}, function () { | |||
titleText.style.opacity = '1'; | |||
}); | }); | ||
} | } | ||
function animateDivider(divider) { | function animateDivider(divider) { | ||
var total = Math.max(0, Math.round(divider.root.getBoundingClientRect().width)); | |||
var leftMax = Math.floor(total / 2); | |||
var rightMax = total - leftMax; | |||
var | |||
return | return animateFrames(DIVIDER_DELAY, DIVIDER_DURATION, function (eased) { | ||
left | divider.left.style.width = Math.round(leftMax * eased) + 'px'; | ||
width | divider.right.style.width = Math.round(rightMax * eased) + 'px'; | ||
}, function () { | |||
divider.left.style.width = leftMax + 'px'; | |||
divider.right.style.width = rightMax + 'px'; | |||
}); | }); | ||
} | } | ||
| 269번째 줄: | 340번째 줄: | ||
function animateLine(line, index) { | function animateLine(line, index) { | ||
var delay = BODY_DELAY + (index * LINE_STAGGER); | var delay = BODY_DELAY + (index * LINE_STAGGER); | ||
var | |||
return animateFrames(delay, LINE_DURATION, function (eased) { | |||
var height = Math.round(LINE_HEIGHT * eased); | |||
line.style.height = height + 'px'; | |||
line.style.maxHeight = height + 'px'; | |||
}, function () { | |||
line.style.height = LINE_HEIGHT + 'px'; | |||
line.style.maxHeight = LINE_HEIGHT + 'px'; | |||
line.style.overflow = 'visible'; | |||
}); | }); | ||
} | } | ||
| 328번째 줄: | 355번째 줄: | ||
var title = portal && portal.querySelector(TITLE_SELECTOR); | var title = portal && portal.querySelector(TITLE_SELECTOR); | ||
var copy = portal && portal.querySelector(COPY_SELECTOR); | var copy = portal && portal.querySelector(COPY_SELECTOR); | ||
var titleText; | |||
var divider; | |||
var total; | |||
var leftMax; | |||
var rightMax; | |||
if (title) { | if (title) { | ||
title.style.opacity = '1'; | titleText = ensureTitleText(title); | ||
title.style. | divider = ensureDivider(title); | ||
titleText.style.opacity = '1'; | |||
total = Math.max(0, Math.round(title.getBoundingClientRect().width)); | |||
leftMax = Math.floor(total / 2); | |||
rightMax = total - leftMax; | |||
divider.left.style.width = leftMax + 'px'; | |||
divider.right.style.width = rightMax + 'px'; | |||
} | } | ||
if (copy) copy.style.visibility = 'visible'; | if (copy) copy.style.visibility = 'visible'; | ||
if (portal) { | if (portal) { | ||
snapManifestoToIntegerGrid(portal); | |||
portal.classList.remove('manifesto-intro-prepared'); | portal.classList.remove('manifesto-intro-prepared'); | ||
portal.classList.add('manifesto-intro-complete'); | portal.classList.add('manifesto-intro-complete'); | ||
setState(portal, 'complete'); | setState(portal, 'complete'); | ||
portal.setAttribute('data-manifesto-intro-played', '1'); | portal.setAttribute('data-manifesto-intro-played', '1'); | ||
portal.setAttribute('data-manifesto-intro-version', VERSION); | |||
} | } | ||
| 348번째 줄: | 388번째 줄: | ||
var title = portal.querySelector(TITLE_SELECTOR); | var title = portal.querySelector(TITLE_SELECTOR); | ||
var copy = portal.querySelector(COPY_SELECTOR); | var copy = portal.querySelector(COPY_SELECTOR); | ||
var titleText; | |||
var divider; | |||
var lines; | var lines; | ||
if (!title || !copy) return null; | if (!title || !copy) return null; | ||
try { | try { | ||
snapManifestoToIntegerGrid(portal); | |||
titleText = ensureTitleText(title); | |||
divider = ensureDivider(title); | |||
lines = buildRenderedLines(copy); | lines = buildRenderedLines(copy); | ||
} catch (error) { | } catch (error) { | ||
restoreOriginalText(copy); | restoreOriginalText(copy); | ||
| 361번째 줄: | 404번째 줄: | ||
} | } | ||
if (!lines.length || !divider) { | if (!lines.length || !divider || !titleText) { | ||
restoreOriginalText(copy); | restoreOriginalText(copy); | ||
return null; | return null; | ||
} | } | ||
applyInitialFrame(portal, | applyInitialFrame(portal, titleText, copy, divider, lines); | ||
return { | return { | ||
titleText: titleText, | |||
copy: copy, | copy: copy, | ||
divider: divider, | divider: divider, | ||
| 381번째 줄: | 424번째 줄: | ||
setState(portal, 'running'); | setState(portal, 'running'); | ||
portal.setAttribute('data-manifesto-intro-played', '1'); | portal.setAttribute('data-manifesto-intro-played', '1'); | ||
portal.setAttribute('data-manifesto-intro-version', VERSION); | |||
animations.push(animateTitle(prepared. | animations.push(animateTitle(prepared.titleText)); | ||
animations.push(animateDivider(prepared.divider)); | animations.push(animateDivider(prepared.divider)); | ||
| 399번째 줄: | 443번째 줄: | ||
function begin(portal) { | function begin(portal) { | ||
var state; | |||
if (!portal || !portal.isConnected) return Promise.resolve(false); | if (!portal || !portal.isConnected) return Promise.resolve(false); | ||
if (portal.getAttribute('data-manifesto-intro-version') !== VERSION) { | |||
portal.removeAttribute('data-manifesto-intro-played'); | |||
portal.removeAttribute('data-manifesto-intro-state'); | |||
portal.classList.remove('manifesto-intro-prepared', 'manifesto-intro-running', 'manifesto-intro-complete'); | |||
if (portalStates) portalStates.delete(portal); | |||
} | |||
state = getState(portal); | |||
if (state === 'preparing' || state === 'running' || state === 'complete') { | if (state === 'preparing' || state === 'running' || state === 'complete') { | ||
return Promise.resolve(false); | return Promise.resolve(false); | ||
} | } | ||
if (prefersReducedMotion() | if (prefersReducedMotion()) { | ||
showImmediately(portal); | showImmediately(portal); | ||
return Promise.resolve(false); | return Promise.resolve(false); | ||
| 414번째 줄: | 467번째 줄: | ||
return waitForFont().then(function () { | return waitForFont().then(function () { | ||
var prepared; | |||
if (!portal.isConnected) return false; | if (!portal.isConnected) return false; | ||
prepared = prepare(portal); | |||
if (!prepared) { | if (!prepared) { | ||
showImmediately(portal); | showImmediately(portal); | ||
| 443번째 줄: | 498번째 줄: | ||
var portal = findPortal(root); | var portal = findPortal(root); | ||
if (!portal) | if (!portal) return Promise.resolve(false); | ||
return begin(portal); | return begin(portal); | ||
} | } | ||
| 455번째 줄: | 507번째 줄: | ||
observer = new MutationObserver(function () { | observer = new MutationObserver(function () { | ||
var portal = document.querySelector(PORTAL_SELECTOR); | var portal = document.querySelector(PORTAL_SELECTOR); | ||
if ( | if (portal) init(portal); | ||
}); | }); | ||
| 484번째 줄: | 535번째 줄: | ||
window.addEventListener('pageshow', function () { | window.addEventListener('pageshow', function () { | ||
boot(document); | boot(document); | ||
}); | |||
window.addEventListener('resize', function () { | |||
window.clearTimeout(resizeTimer); | |||
resizeTimer = window.setTimeout(function () { | |||
var portal = document.querySelector(PORTAL_SELECTOR); | |||
if (portal) snapManifestoToIntegerGrid(portal); | |||
}, 80); | |||
}); | }); | ||
| 493번째 줄: | 552번째 줄: | ||
window.ManifestoIntro = { | window.ManifestoIntro = { | ||
version: | version: VERSION, | ||
init: init, | init: init, | ||
status: function () { | status: function () { | ||
var portal = document.querySelector(PORTAL_SELECTOR); | var portal = document.querySelector(PORTAL_SELECTOR); | ||
var title = portal && portal.querySelector(TITLE_SELECTOR); | |||
var copy = portal && portal.querySelector(COPY_SELECTOR); | |||
var titleRect = title ? title.getBoundingClientRect() : null; | |||
var copyRect = copy ? copy.getBoundingClientRect() : null; | |||
return { | return { | ||
loaded: true, | loaded: true, | ||
version: VERSION, | |||
portal: !!portal, | portal: !!portal, | ||
state: portal ? getState(portal) : '', | state: portal ? getState(portal) : '', | ||
| 504번째 줄: | 569번째 줄: | ||
running: !!(portal && portal.classList.contains('manifesto-intro-running')), | running: !!(portal && portal.classList.contains('manifesto-intro-running')), | ||
complete: !!(portal && portal.classList.contains('manifesto-intro-complete')), | complete: !!(portal && portal.classList.contains('manifesto-intro-complete')), | ||
lineCount: portal ? portal.querySelectorAll('.manifesto-line').length : 0 | lineCount: portal ? portal.querySelectorAll('.manifesto-line').length : 0, | ||
titleX: titleRect ? titleRect.x : null, | |||
copyX: copyRect ? copyRect.x : null, | |||
titleXIsInteger: titleRect ? Number.isInteger(titleRect.x) : null, | |||
copyXIsInteger: copyRect ? Number.isInteger(copyRect.x) : null | |||
}; | }; | ||
} | } | ||
}; | }; | ||
}(window, document, window.mw)); | }(window, document, window.mw)); | ||
2026년 7월 13일 (월) 06:51 판
/* =========================================
ManifestoIntro
대문 선언문 절차적 진입 애니메이션
========================================= */
/*
새 기능과 공개 이름에는 프로젝트 접두사를 사용하지 않는다.
실제 HTML 텍스트를 유지한 채 제목, 구분선, 본문 행을 순서대로 연다.
이미지, canvas, 글자 래스터화는 사용하지 않는다.
픽셀 폰트 글리프에는 transform을 적용하지 않고 정수 CSS px만 사용한다.
*/
(function (window, document, mw) {
'use strict';
var VERSION = '20260713-procedural-004-integer-grid';
var PORTAL_SELECTOR = '.main-portal';
var TITLE_SELECTOR = '.main-manifesto-title';
var COPY_SELECTOR = '.main-manifesto-copy';
var FONT_QUERY = '18px "NanumMyeongjoBold Pixel 18"';
var TITLE_DELAY = 50;
var TITLE_DURATION = 420;
var DIVIDER_DELAY = 190;
var DIVIDER_DURATION = 500;
var BODY_DELAY = DIVIDER_DELAY + DIVIDER_DURATION + 30;
var LINE_STAGGER = 64;
var LINE_DURATION = 230;
var LINE_HEIGHT = 26;
var INNER_WIDTH = 660;
var COPY_WIDTH = 620;
var portalStates = typeof WeakMap === 'function' ? new WeakMap() : null;
var observer = null;
var resizeTimer = 0;
function findPortal(root) {
var scope = root && root.querySelector ? root : document;
if (scope.matches && scope.matches(PORTAL_SELECTOR)) {
return scope;
}
return scope.querySelector(PORTAL_SELECTOR) || document.querySelector(PORTAL_SELECTOR);
}
function prefersReducedMotion() {
return !!(
window.matchMedia &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches
);
}
function getState(portal) {
if (portalStates) return portalStates.get(portal) || '';
return portal.getAttribute('data-manifesto-intro-state') || '';
}
function setState(portal, state) {
if (portalStates) portalStates.set(portal, state);
portal.setAttribute('data-manifesto-intro-state', state);
}
function disarmPending() {
document.documentElement.classList.remove('manifesto-intro-pending');
document.documentElement.classList.remove('manifesto-intro-active');
}
function waitForFont() {
if (!document.fonts || typeof document.fonts.load !== 'function') {
return Promise.resolve();
}
var timeout = new Promise(function (resolve) {
window.setTimeout(resolve, 1400);
});
var load = document.fonts.load(FONT_QUERY).then(function () {
return document.fonts.ready;
}).catch(function () {
return null;
});
return Promise.race([load, timeout]);
}
function snapManifestoToIntegerGrid(portal) {
var manifesto = portal.querySelector('.main-manifesto');
var inner = portal.querySelector('.main-manifesto-inner');
var copy = portal.querySelector(COPY_SELECTOR);
var available;
var left;
if (!manifesto || !inner || !copy) return;
available = Math.floor(manifesto.clientWidth || manifesto.getBoundingClientRect().width || 0);
left = available > INNER_WIDTH ? Math.round((available - INNER_WIDTH) / 2) : 0;
manifesto.style.setProperty('justify-content', 'flex-start');
inner.style.setProperty('margin', '0', 'important');
inner.style.setProperty('margin-left', left + 'px', 'important');
inner.style.setProperty('transform', 'none', 'important');
copy.style.setProperty('margin', '0', 'important');
copy.style.setProperty('margin-left', Math.round((INNER_WIDTH - COPY_WIDTH) / 2) + 'px', 'important');
copy.style.setProperty('transform', 'none', 'important');
}
function tokenizeParagraph(paragraph) {
var text = String(paragraph.textContent || '').replace(/\s+/g, ' ').trim();
var words = text ? text.split(' ') : [];
paragraph.textContent = '';
paragraph.setAttribute('data-manifesto-original-text', text);
words.forEach(function (word, index) {
var token = document.createElement('span');
token.className = 'manifesto-measure-token';
token.textContent = word;
paragraph.appendChild(token);
if (index < words.length - 1) {
paragraph.appendChild(document.createTextNode(' '));
}
});
return Array.prototype.slice.call(
paragraph.querySelectorAll('.manifesto-measure-token')
);
}
function groupTokensIntoRows(tokens) {
var rows = [];
var current = null;
tokens.forEach(function (token) {
var top = Math.round(token.getBoundingClientRect().top);
if (!current || current.top !== top) {
current = { top: top, words: [] };
rows.push(current);
}
current.words.push(token.textContent);
});
return rows.map(function (row) {
return row.words.join(' ');
});
}
function createLine(text, index) {
var line = document.createElement('span');
var inner = document.createElement('span');
line.className = 'manifesto-line';
line.setAttribute('data-manifesto-line-index', String(index));
inner.className = 'manifesto-line-inner';
inner.textContent = text;
line.appendChild(inner);
return line;
}
function buildRenderedLines(copy) {
var paragraphs = Array.prototype.slice.call(copy.querySelectorAll('p'));
var lines = [];
var lineIndex = 0;
if (!paragraphs.length) return lines;
copy.classList.add('manifesto-line-measuring');
paragraphs.forEach(function (paragraph) {
var tokens = tokenizeParagraph(paragraph);
var rows = groupTokensIntoRows(tokens);
paragraph.textContent = '';
rows.forEach(function (text) {
var line = createLine(text, lineIndex);
paragraph.appendChild(line);
lines.push(line);
lineIndex += 1;
});
});
copy.classList.remove('manifesto-line-measuring');
return lines;
}
function restoreOriginalText(copy) {
Array.prototype.forEach.call(copy.querySelectorAll('p'), function (paragraph) {
var original = paragraph.getAttribute('data-manifesto-original-text');
if (original !== null) {
paragraph.textContent = original;
paragraph.removeAttribute('data-manifesto-original-text');
}
});
}
function ensureTitleText(title) {
var existing = title.querySelector(':scope > .manifesto-title-text');
var divider = title.querySelector(':scope > .manifesto-divider');
var wrapper;
var nodes;
if (existing) return existing;
wrapper = document.createElement('span');
wrapper.className = 'manifesto-title-text';
nodes = Array.prototype.slice.call(title.childNodes);
nodes.forEach(function (node) {
if (node !== divider) wrapper.appendChild(node);
});
if (divider) {
title.insertBefore(wrapper, divider);
} else {
title.appendChild(wrapper);
}
return wrapper;
}
function ensureDivider(title) {
var divider = title.querySelector(':scope > .manifesto-divider');
var left;
var right;
if (!divider) {
divider = document.createElement('span');
divider.className = 'manifesto-divider';
divider.setAttribute('aria-hidden', 'true');
title.appendChild(divider);
}
left = divider.querySelector('.manifesto-divider-left');
right = divider.querySelector('.manifesto-divider-right');
if (!left || !right) {
divider.textContent = '';
left = document.createElement('span');
right = document.createElement('span');
left.className = 'manifesto-divider-half manifesto-divider-left';
right.className = 'manifesto-divider-half manifesto-divider-right';
divider.appendChild(left);
divider.appendChild(right);
}
return {
root: divider,
left: left,
right: right
};
}
function applyInitialFrame(portal, titleText, copy, divider, lines) {
portal.classList.remove('manifesto-intro-complete');
portal.classList.add('manifesto-intro-prepared');
document.documentElement.classList.add('manifesto-intro-active');
titleText.style.opacity = '0';
divider.left.style.width = '0px';
divider.right.style.width = '0px';
lines.forEach(function (line) {
var inner = line.querySelector('.manifesto-line-inner');
line.style.height = '0px';
line.style.maxHeight = '0px';
line.style.overflow = 'hidden';
line.style.opacity = '1';
line.style.clipPath = 'none';
if (inner) {
inner.style.opacity = '1';
inner.style.transform = 'none';
}
});
copy.style.visibility = 'visible';
portal.getBoundingClientRect();
lines.forEach(function (line) { line.getBoundingClientRect(); });
document.documentElement.classList.remove('manifesto-intro-pending');
}
function easeOutCubic(value) {
var inverse = 1 - value;
return 1 - (inverse * inverse * inverse);
}
function animateFrames(delay, duration, update, complete) {
return new Promise(function (resolve) {
window.setTimeout(function () {
var start = window.performance && performance.now ? performance.now() : Date.now();
function frame(now) {
var current = typeof now === 'number' ? now : Date.now();
var progress = Math.max(0, Math.min(1, (current - start) / duration));
update(easeOutCubic(progress), progress);
if (progress < 1) {
window.requestAnimationFrame(frame);
} else {
if (complete) complete();
resolve();
}
}
window.requestAnimationFrame(frame);
}, delay);
});
}
function animateTitle(titleText) {
return animateFrames(TITLE_DELAY, TITLE_DURATION, function (eased) {
titleText.style.opacity = String(Math.round(eased * 1000) / 1000);
}, function () {
titleText.style.opacity = '1';
});
}
function animateDivider(divider) {
var total = Math.max(0, Math.round(divider.root.getBoundingClientRect().width));
var leftMax = Math.floor(total / 2);
var rightMax = total - leftMax;
return animateFrames(DIVIDER_DELAY, DIVIDER_DURATION, function (eased) {
divider.left.style.width = Math.round(leftMax * eased) + 'px';
divider.right.style.width = Math.round(rightMax * eased) + 'px';
}, function () {
divider.left.style.width = leftMax + 'px';
divider.right.style.width = rightMax + 'px';
});
}
function animateLine(line, index) {
var delay = BODY_DELAY + (index * LINE_STAGGER);
return animateFrames(delay, LINE_DURATION, function (eased) {
var height = Math.round(LINE_HEIGHT * eased);
line.style.height = height + 'px';
line.style.maxHeight = height + 'px';
}, function () {
line.style.height = LINE_HEIGHT + 'px';
line.style.maxHeight = LINE_HEIGHT + 'px';
line.style.overflow = 'visible';
});
}
function showImmediately(portal) {
var title = portal && portal.querySelector(TITLE_SELECTOR);
var copy = portal && portal.querySelector(COPY_SELECTOR);
var titleText;
var divider;
var total;
var leftMax;
var rightMax;
if (title) {
titleText = ensureTitleText(title);
divider = ensureDivider(title);
titleText.style.opacity = '1';
total = Math.max(0, Math.round(title.getBoundingClientRect().width));
leftMax = Math.floor(total / 2);
rightMax = total - leftMax;
divider.left.style.width = leftMax + 'px';
divider.right.style.width = rightMax + 'px';
}
if (copy) copy.style.visibility = 'visible';
if (portal) {
snapManifestoToIntegerGrid(portal);
portal.classList.remove('manifesto-intro-prepared');
portal.classList.add('manifesto-intro-complete');
setState(portal, 'complete');
portal.setAttribute('data-manifesto-intro-played', '1');
portal.setAttribute('data-manifesto-intro-version', VERSION);
}
disarmPending();
}
function prepare(portal) {
var title = portal.querySelector(TITLE_SELECTOR);
var copy = portal.querySelector(COPY_SELECTOR);
var titleText;
var divider;
var lines;
if (!title || !copy) return null;
try {
snapManifestoToIntegerGrid(portal);
titleText = ensureTitleText(title);
divider = ensureDivider(title);
lines = buildRenderedLines(copy);
} catch (error) {
restoreOriginalText(copy);
return null;
}
if (!lines.length || !divider || !titleText) {
restoreOriginalText(copy);
return null;
}
applyInitialFrame(portal, titleText, copy, divider, lines);
return {
titleText: titleText,
copy: copy,
divider: divider,
lines: lines
};
}
function play(portal, prepared) {
var animations = [];
setState(portal, 'running');
portal.setAttribute('data-manifesto-intro-played', '1');
portal.setAttribute('data-manifesto-intro-version', VERSION);
animations.push(animateTitle(prepared.titleText));
animations.push(animateDivider(prepared.divider));
prepared.lines.forEach(function (line, index) {
animations.push(animateLine(line, index));
});
return Promise.all(animations).then(function () {
portal.classList.add('manifesto-intro-complete');
portal.classList.remove('manifesto-intro-running');
setState(portal, 'complete');
document.documentElement.classList.remove('manifesto-intro-active');
return true;
});
}
function begin(portal) {
var state;
if (!portal || !portal.isConnected) return Promise.resolve(false);
if (portal.getAttribute('data-manifesto-intro-version') !== VERSION) {
portal.removeAttribute('data-manifesto-intro-played');
portal.removeAttribute('data-manifesto-intro-state');
portal.classList.remove('manifesto-intro-prepared', 'manifesto-intro-running', 'manifesto-intro-complete');
if (portalStates) portalStates.delete(portal);
}
state = getState(portal);
if (state === 'preparing' || state === 'running' || state === 'complete') {
return Promise.resolve(false);
}
if (prefersReducedMotion()) {
showImmediately(portal);
return Promise.resolve(false);
}
setState(portal, 'preparing');
return waitForFont().then(function () {
var prepared;
if (!portal.isConnected) return false;
prepared = prepare(portal);
if (!prepared) {
showImmediately(portal);
return false;
}
portal.classList.add('manifesto-intro-running');
return new Promise(function (resolve) {
window.requestAnimationFrame(function () {
window.requestAnimationFrame(function () {
play(portal, prepared).then(resolve).catch(function () {
showImmediately(portal);
resolve(false);
});
});
});
});
}).catch(function () {
showImmediately(portal);
return false;
});
}
function init(root) {
var portal = findPortal(root);
if (!portal) return Promise.resolve(false);
return begin(portal);
}
function observeForPortal() {
if (observer || !window.MutationObserver || !document.documentElement) return;
observer = new MutationObserver(function () {
var portal = document.querySelector(PORTAL_SELECTOR);
if (portal) init(portal);
});
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
}
function boot(root) {
init(root || document);
observeForPortal();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () {
boot(document);
}, { once: true });
} else {
boot(document);
}
window.addEventListener('load', function () {
boot(document);
}, { once: true });
window.addEventListener('pageshow', function () {
boot(document);
});
window.addEventListener('resize', function () {
window.clearTimeout(resizeTimer);
resizeTimer = window.setTimeout(function () {
var portal = document.querySelector(PORTAL_SELECTOR);
if (portal) snapManifestoToIntegerGrid(portal);
}, 80);
});
if (mw && mw.hook) {
mw.hook('wikipage.content').add(function ($content) {
boot($content && $content[0] ? $content[0] : document);
});
}
window.ManifestoIntro = {
version: VERSION,
init: init,
status: function () {
var portal = document.querySelector(PORTAL_SELECTOR);
var title = portal && portal.querySelector(TITLE_SELECTOR);
var copy = portal && portal.querySelector(COPY_SELECTOR);
var titleRect = title ? title.getBoundingClientRect() : null;
var copyRect = copy ? copy.getBoundingClientRect() : null;
return {
loaded: true,
version: VERSION,
portal: !!portal,
state: portal ? getState(portal) : '',
prepared: !!(portal && portal.classList.contains('manifesto-intro-prepared')),
running: !!(portal && portal.classList.contains('manifesto-intro-running')),
complete: !!(portal && portal.classList.contains('manifesto-intro-complete')),
lineCount: portal ? portal.querySelectorAll('.manifesto-line').length : 0,
titleX: titleRect ? titleRect.x : null,
copyX: copyRect ? copyRect.x : null,
titleXIsInteger: titleRect ? Number.isInteger(titleRect.x) : null,
copyXIsInteger: copyRect ? Number.isInteger(copyRect.x) : null
};
}
};
}(window, document, window.mw));