(Install package: clbiwiki-mainpage-divider-two-line-controlled-fix-20260713 / js/ManifestoIntro.js) |
(Install package: clbiwiki-mainpage-divider-follow-text-fix-20260713 / js/ManifestoIntro.js) |
||
| 298번째 줄: | 298번째 줄: | ||
} | } | ||
function | function measureDropGeometry(copy, divider) { | ||
var lines = Array.prototype.slice.call(copy.querySelectorAll('.manifesto-line')); | var lines = Array.prototype.slice.call(copy.querySelectorAll('.manifesto-line')); | ||
var savedStyles = lines.map(function (line) { | var savedStyles = lines.map(function (line) { | ||
| 305번째 줄: | 305번째 줄: | ||
var dividerRect; | var dividerRect; | ||
var copyRect; | var copyRect; | ||
var lineRects; | |||
var gapsBefore; | |||
var distance; | var distance; | ||
dividerRect = divider.getBoundingClientRect(); | |||
lines.forEach(function (line) { | lines.forEach(function (line) { | ||
| 314번째 줄: | 318번째 줄: | ||
}); | }); | ||
copyRect = copy.getBoundingClientRect(); | copyRect = copy.getBoundingClientRect(); | ||
distance = Math.max(0, Math.round(copyRect.bottom - dividerRect.top)); | lineRects = lines.map(function (line) { return line.getBoundingClientRect(); }); | ||
gapsBefore = lineRects.map(function (rect, index) { | |||
if (index === 0) return Math.max(0, rect.top - dividerRect.top); | |||
return Math.max(0, rect.top - lineRects[index - 1].bottom); | |||
}); | |||
distance = lineRects.length | |||
? Math.max(0, Math.round(lineRects[lineRects.length - 1].bottom - dividerRect.top)) | |||
: Math.max(0, Math.round(copyRect.bottom - dividerRect.top)); | |||
lines.forEach(function (line, index) { | lines.forEach(function (line, index) { | ||
| 323번째 줄: | 333번째 줄: | ||
}); | }); | ||
return distance; | return { | ||
originTop: dividerRect.top, | |||
gapsBefore: gapsBefore, | |||
finalDistance: distance | |||
}; | |||
} | |||
function measureDropDistance(copy, divider) { | |||
return measureDropGeometry(copy, divider).finalDistance; | |||
} | } | ||
| 420번째 줄: | 438번째 줄: | ||
} | } | ||
function animateDropDivider(drop, | function stopDropTracking(drop, finishAtEnd) { | ||
var resolve; | |||
if (!drop) return; | |||
if (drop.__manifestoDropTimer) { | |||
window.clearTimeout(drop.__manifestoDropTimer); | |||
drop.__manifestoDropTimer = 0; | |||
} | |||
if (drop.__manifestoDropFrame) { | |||
window.cancelAnimationFrame(drop.__manifestoDropFrame); | |||
drop.__manifestoDropFrame = 0; | |||
} | |||
if (finishAtEnd && Number.isFinite(drop.__manifestoDropFinalDistance)) { | |||
drop.style.opacity = '1'; | |||
drop.style.transform = 'translateY(' + Math.round(drop.__manifestoDropFinalDistance) + 'px)'; | |||
} | |||
resolve = drop.__manifestoDropResolve; | |||
drop.__manifestoDropResolve = null; | |||
if (typeof resolve === 'function') resolve(); | |||
} | |||
function animateDropDivider(drop, copy, geometry, lines) { | |||
var splitAt = DIVIDER_DELAY + DIVIDER_DURATION; | var splitAt = DIVIDER_DELAY + DIVIDER_DURATION; | ||
var | var finalDistance = geometry ? geometry.finalDistance : 0; | ||
if (!drop) return Promise.resolve(); | if (!drop || !copy || !geometry) return Promise.resolve(); | ||
/* | /* | ||
위 구분선이 100%가 되는 순간 같은 위치에서 두 번째 선을 드러낸다. | 위 구분선이 100%가 되는 순간 같은 위치에서 두 번째 선을 드러낸다. | ||
두 번째 선에는 독립 easing을 주지 않는다. 매 프레임 실제 copy 높이와 | |||
첫 행의 열린 높이를 읽어 현재 본문 하단만 따라가게 한다. | |||
*/ | */ | ||
stopDropTracking(drop, false); | |||
{ | drop.__manifestoDropFinalDistance = finalDistance; | ||
return new Promise(function (resolve) { | |||
var lastDistance = 0; | |||
var settled = false; | |||
function finish(atEnd) { | |||
if (settled) return; | |||
settled = true; | |||
drop.__manifestoDropResolve = null; | |||
if (atEnd) { | |||
drop.style.opacity = '1'; | |||
drop.style.transform = 'translateY(' + Math.round(finalDistance) + 'px)'; | |||
} | |||
resolve(); | |||
} | |||
function track() { | |||
var distance = 0; | |||
if (!drop.isConnected || !copy.isConnected) { | |||
finish(false); | |||
return; | |||
} | |||
lines.forEach(function (line, index) { | |||
var height = line.getBoundingClientRect().height; | |||
var progress = Math.max(0, Math.min(1, height / LINE_HEIGHT)); | |||
/* 각 행 앞의 실제 여백도 그 행이 열리는 비율만큼만 함께 연다. */ | |||
distance += ((geometry.gapsBefore[index] || 0) * progress) + height; | |||
}); | |||
distance = Math.max(lastDistance, Math.min(finalDistance, Math.round(distance))); | |||
lastDistance = distance; | |||
drop.style.opacity = '1'; | |||
drop.style.transform = 'translateY(' + distance + 'px)'; | |||
if (distance >= finalDistance) { | |||
finish(true); | |||
return; | |||
} | |||
drop.__manifestoDropFrame = window.requestAnimationFrame(track); | |||
} | |||
drop.__manifestoDropResolve = function () { finish(false); }; | |||
drop.__manifestoDropTimer = window.setTimeout(function () { | |||
drop.__manifestoDropTimer = 0; | |||
drop.style.opacity = '1'; | |||
drop.__manifestoDropFrame = window.requestAnimationFrame(track); | |||
}, splitAt); | |||
}); | }); | ||
} | } | ||
| 523번째 줄: | 603번째 줄: | ||
), | ), | ||
function (node) { | function (node) { | ||
if (node.classList && node.classList.contains('manifesto-divider-drop')) { | |||
stopDropTracking(node, false); | |||
} | |||
if (typeof node.getAnimations !== 'function') return; | if (typeof node.getAnimations !== 'function') return; | ||
node.getAnimations().forEach(function (animation) { | node.getAnimations().forEach(function (animation) { | ||
| 590번째 줄: | 673번째 줄: | ||
var divider; | var divider; | ||
var drop; | var drop; | ||
var | var dropGeometry; | ||
if (!title || !copy) return null; | if (!title || !copy) return null; | ||
| 614번째 줄: | 697번째 줄: | ||
drop.style.transform = 'translateY(0px)'; | drop.style.transform = 'translateY(0px)'; | ||
positionDropAtDivider(drop, divider); | positionDropAtDivider(drop, divider); | ||
dropGeometry = measureDropGeometry(copy, divider); | |||
return { | return { | ||
| 621번째 줄: | 704번째 줄: | ||
divider: divider, | divider: divider, | ||
drop: drop, | drop: drop, | ||
dropGeometry: dropGeometry, | |||
lines: lines | lines: lines | ||
}; | }; | ||
| 636번째 줄: | 719번째 줄: | ||
animations.push(animateDropDivider( | animations.push(animateDropDivider( | ||
prepared.drop, | prepared.drop, | ||
prepared. | prepared.copy, | ||
prepared.lines | prepared.dropGeometry, | ||
prepared.lines | |||
)); | )); | ||
| 760번째 줄: | 844번째 줄: | ||
window.ManifestoIntro = { | window.ManifestoIntro = { | ||
version: '20260713-divider- | version: '20260713-divider-follow-text-004', | ||
init: init, | init: init, | ||
status: function () { | status: function () { | ||
2026년 7월 13일 (월) 14:33 판
/* =========================================
ManifestoIntro
대문 선언문 절차적 진입 애니메이션
========================================= */
/*
새 기능과 공개 이름에는 프로젝트 접두사를 사용하지 않는다.
실제 HTML 텍스트를 유지한 채 제목, 구분선, 본문 행을 순서대로 연다.
이미지, canvas, 글자 래스터화는 사용하지 않는다.
*/
(function (window, document, mw) {
'use strict';
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 ANIMATION_WATCHDOG_MS = 6000;
var portalStates = typeof WeakMap === 'function' ? new WeakMap() : null;
var portalWatchdogs = typeof WeakMap === 'function' ? new WeakMap() : null;
var observer = null;
function getWatchdog(portal) {
if (!portal) return 0;
if (portalWatchdogs) return portalWatchdogs.get(portal) || 0;
return portal.__manifestoIntroWatchdog || 0;
}
function clearWatchdog(portal) {
var timer = getWatchdog(portal);
if (timer) window.clearTimeout(timer);
if (portalWatchdogs && portal) portalWatchdogs.delete(portal);
if (portal) portal.__manifestoIntroWatchdog = 0;
}
function armWatchdog(portal) {
var timer;
if (!portal) return;
clearWatchdog(portal);
timer = window.setTimeout(function () {
var state = getState(portal);
if (state === 'preparing' || state === 'running') {
showImmediately(portal);
}
}, ANIMATION_WATCHDOG_MS);
if (portalWatchdogs) portalWatchdogs.set(portal, timer);
else portal.__manifestoIntroWatchdog = timer;
}
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 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 || Math.abs(current.top - top) > 1) {
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 sanitizeDividerSources(title) {
var inner = title && title.closest ? title.closest('.main-manifesto-inner') : null;
var portal = title && title.closest ? title.closest('.main-portal') : null;
if (!title || !inner) return;
/* 제목 자체가 그리던 완성형 선을 런타임에서도 확실하게 끊는다. */
title.style.setProperty('border-bottom', '0', 'important');
title.style.setProperty('box-shadow', 'none', 'important');
title.style.setProperty('background-image', 'none', 'important');
title.style.setProperty('text-decoration', 'none', 'important');
/* 이전 버전이 남긴 모든 선과 bitmap을 지운 뒤 두 실선만 다시 만든다. */
Array.prototype.forEach.call(
inner.querySelectorAll(
'.manifesto-divider, .manifesto-divider-drop, ' +
'.main-manifesto-divider, [data-manifesto-divider-track], ' +
'canvas.main-manifesto-bitmap'
),
function (node) {
if (node.parentNode) node.parentNode.removeChild(node);
}
);
Array.prototype.forEach.call(inner.children || [], function (child) {
if (child && child.tagName === 'HR' && child.parentNode) {
child.parentNode.removeChild(child);
}
});
if (portal) portal.classList.add('manifesto-divider-controlled');
}
function ensureDivider(title) {
var divider = title.querySelector('.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) {
left = document.createElement('span');
left.className = 'manifesto-divider-half manifesto-divider-left';
divider.appendChild(left);
}
if (!right) {
right = document.createElement('span');
right.className = 'manifesto-divider-half manifesto-divider-right';
divider.appendChild(right);
}
return divider;
}
function ensureDropDivider(title) {
var inner = title && title.closest ? title.closest('.main-manifesto-inner') : null;
var drop;
if (!inner) return null;
drop = inner.querySelector(':scope > .manifesto-divider-drop');
if (!drop) {
drop = document.createElement('span');
drop.className = 'manifesto-divider-drop';
drop.setAttribute('aria-hidden', 'true');
inner.appendChild(drop);
}
return drop;
}
function positionDropAtDivider(drop, divider) {
var inner;
var innerRect;
var dividerRect;
if (!drop || !divider) return false;
inner = drop.parentNode;
if (!inner || typeof inner.getBoundingClientRect !== 'function') return false;
innerRect = inner.getBoundingClientRect();
dividerRect = divider.getBoundingClientRect();
drop.style.top = Math.round(dividerRect.top - innerRect.top) + 'px';
drop.style.left = '0px';
drop.style.width = Math.round(innerRect.width) + 'px';
return true;
}
function measureDropGeometry(copy, divider) {
var lines = Array.prototype.slice.call(copy.querySelectorAll('.manifesto-line'));
var savedStyles = lines.map(function (line) {
return line.getAttribute('style');
});
var dividerRect;
var copyRect;
var lineRects;
var gapsBefore;
var distance;
dividerRect = divider.getBoundingClientRect();
lines.forEach(function (line) {
line.style.height = LINE_HEIGHT + 'px';
line.style.maxHeight = LINE_HEIGHT + 'px';
line.style.overflow = 'visible';
line.style.clipPath = 'inset(0 0 0 0)';
});
copyRect = copy.getBoundingClientRect();
lineRects = lines.map(function (line) { return line.getBoundingClientRect(); });
gapsBefore = lineRects.map(function (rect, index) {
if (index === 0) return Math.max(0, rect.top - dividerRect.top);
return Math.max(0, rect.top - lineRects[index - 1].bottom);
});
distance = lineRects.length
? Math.max(0, Math.round(lineRects[lineRects.length - 1].bottom - dividerRect.top))
: Math.max(0, Math.round(copyRect.bottom - dividerRect.top));
lines.forEach(function (line, index) {
if (savedStyles[index] === null) line.removeAttribute('style');
else line.setAttribute('style', savedStyles[index]);
});
return {
originTop: dividerRect.top,
gapsBefore: gapsBefore,
finalDistance: distance
};
}
function measureDropDistance(copy, divider) {
return measureDropGeometry(copy, divider).finalDistance;
}
function applyInitialFrame(portal, title, copy, divider, lines) {
portal.classList.remove('manifesto-intro-complete');
portal.classList.add('manifesto-intro-prepared');
document.documentElement.classList.add('manifesto-intro-active');
title.style.opacity = '0';
divider.style.left = '0';
divider.style.width = '100%';
divider.style.background = 'transparent';
Array.prototype.forEach.call(
divider.querySelectorAll('.manifesto-divider-half'),
function (half) {
half.style.transform = 'scaleX(0)';
}
);
lines.forEach(function (line) {
var inner = line.querySelector('.manifesto-line-inner');
line.style.height = '0px';
line.style.maxHeight = '0px';
line.style.opacity = '0';
line.style.overflow = 'hidden';
line.style.clipPath = 'inset(0 0 100% 0)';
if (inner) {
inner.style.opacity = '0.18';
inner.style.transform = 'translateY(-10px)';
}
});
copy.style.visibility = 'visible';
/* 초기 프레임을 실제 렌더 트리에 확정한 뒤 pending을 해제한다. */
portal.getBoundingClientRect();
lines.forEach(function (line) { line.getBoundingClientRect(); });
document.documentElement.classList.remove('manifesto-intro-pending');
}
function finishAnimation(animation, element, finalStyles) {
if (!animation || !animation.finished) return Promise.resolve();
return animation.finished.catch(function () {
return null;
}).then(function () {
Object.keys(finalStyles).forEach(function (property) {
element.style[property] = finalStyles[property];
});
animation.cancel();
});
}
function animateTitle(title) {
var animation = title.animate([
{ opacity: 0 },
{ opacity: 1 }
], {
duration: TITLE_DURATION,
delay: TITLE_DELAY,
easing: 'cubic-bezier(.18,.72,.24,1)',
fill: 'both'
});
return finishAnimation(animation, title, { opacity: '1' });
}
function animateDivider(divider) {
var halves = divider.querySelectorAll('.manifesto-divider-half');
var animations = [];
/*
최종 길이의 선이나 트랙을 미리 그리지 않는다.
중앙에서 시작하는 좌·우 두 실선 자체만 바깥 방향으로 펼친다.
*/
Array.prototype.forEach.call(halves, function (half) {
var animation = half.animate([
{ transform: 'scaleX(0)' },
{ transform: 'scaleX(1)' }
], {
duration: DIVIDER_DURATION,
delay: DIVIDER_DELAY,
easing: 'cubic-bezier(.2,.78,.22,1)',
fill: 'both'
});
animations.push(finishAnimation(animation, half, {
transform: 'scaleX(1)'
}));
});
return Promise.all(animations);
}
function stopDropTracking(drop, finishAtEnd) {
var resolve;
if (!drop) return;
if (drop.__manifestoDropTimer) {
window.clearTimeout(drop.__manifestoDropTimer);
drop.__manifestoDropTimer = 0;
}
if (drop.__manifestoDropFrame) {
window.cancelAnimationFrame(drop.__manifestoDropFrame);
drop.__manifestoDropFrame = 0;
}
if (finishAtEnd && Number.isFinite(drop.__manifestoDropFinalDistance)) {
drop.style.opacity = '1';
drop.style.transform = 'translateY(' + Math.round(drop.__manifestoDropFinalDistance) + 'px)';
}
resolve = drop.__manifestoDropResolve;
drop.__manifestoDropResolve = null;
if (typeof resolve === 'function') resolve();
}
function animateDropDivider(drop, copy, geometry, lines) {
var splitAt = DIVIDER_DELAY + DIVIDER_DURATION;
var finalDistance = geometry ? geometry.finalDistance : 0;
if (!drop || !copy || !geometry) return Promise.resolve();
/*
위 구분선이 100%가 되는 순간 같은 위치에서 두 번째 선을 드러낸다.
두 번째 선에는 독립 easing을 주지 않는다. 매 프레임 실제 copy 높이와
첫 행의 열린 높이를 읽어 현재 본문 하단만 따라가게 한다.
*/
stopDropTracking(drop, false);
drop.__manifestoDropFinalDistance = finalDistance;
return new Promise(function (resolve) {
var lastDistance = 0;
var settled = false;
function finish(atEnd) {
if (settled) return;
settled = true;
drop.__manifestoDropResolve = null;
if (atEnd) {
drop.style.opacity = '1';
drop.style.transform = 'translateY(' + Math.round(finalDistance) + 'px)';
}
resolve();
}
function track() {
var distance = 0;
if (!drop.isConnected || !copy.isConnected) {
finish(false);
return;
}
lines.forEach(function (line, index) {
var height = line.getBoundingClientRect().height;
var progress = Math.max(0, Math.min(1, height / LINE_HEIGHT));
/* 각 행 앞의 실제 여백도 그 행이 열리는 비율만큼만 함께 연다. */
distance += ((geometry.gapsBefore[index] || 0) * progress) + height;
});
distance = Math.max(lastDistance, Math.min(finalDistance, Math.round(distance)));
lastDistance = distance;
drop.style.opacity = '1';
drop.style.transform = 'translateY(' + distance + 'px)';
if (distance >= finalDistance) {
finish(true);
return;
}
drop.__manifestoDropFrame = window.requestAnimationFrame(track);
}
drop.__manifestoDropResolve = function () { finish(false); };
drop.__manifestoDropTimer = window.setTimeout(function () {
drop.__manifestoDropTimer = 0;
drop.style.opacity = '1';
drop.__manifestoDropFrame = window.requestAnimationFrame(track);
}, splitAt);
});
}
function animateLine(line, index) {
var delay = BODY_DELAY + (index * LINE_STAGGER);
var inner = line.querySelector('.manifesto-line-inner');
var outerAnimation = line.animate([
{
height: '0px',
maxHeight: '0px',
opacity: 0,
clipPath: 'inset(0 0 100% 0)'
},
{
offset: 0.42,
height: '11px',
maxHeight: '11px',
opacity: 0.56,
clipPath: 'inset(0 0 58% 0)'
},
{
height: LINE_HEIGHT + 'px',
maxHeight: LINE_HEIGHT + 'px',
opacity: 1,
clipPath: 'inset(0 0 0 0)'
}
], {
duration: LINE_DURATION,
delay: delay,
easing: 'cubic-bezier(.16,.76,.22,1)',
fill: 'both'
});
var promises = [finishAnimation(outerAnimation, line, {
height: LINE_HEIGHT + 'px',
maxHeight: LINE_HEIGHT + 'px',
opacity: '1',
overflow: 'visible',
clipPath: 'inset(0 0 0 0)'
})];
if (inner) {
var innerAnimation = inner.animate([
{ transform: 'translateY(-10px)', opacity: 0.18 },
{ offset: 0.5, transform: 'translateY(-3px)', opacity: 0.72 },
{ transform: 'translateY(0)', opacity: 1 }
], {
duration: LINE_DURATION,
delay: delay,
easing: 'cubic-bezier(.16,.76,.22,1)',
fill: 'both'
});
promises.push(finishAnimation(innerAnimation, inner, {
transform: 'translateY(0)',
opacity: '1'
}));
}
return Promise.all(promises);
}
function showImmediately(portal) {
var title = portal && portal.querySelector(TITLE_SELECTOR);
var copy = portal && portal.querySelector(COPY_SELECTOR);
var divider = null;
var drop = null;
var dropDistance = 0;
clearWatchdog(portal);
if (portal) {
Array.prototype.forEach.call(
portal.querySelectorAll(
'.main-manifesto-title, .manifesto-divider-half, ' +
'.manifesto-divider-drop, .manifesto-line, .manifesto-line-inner'
),
function (node) {
if (node.classList && node.classList.contains('manifesto-divider-drop')) {
stopDropTracking(node, false);
}
if (typeof node.getAnimations !== 'function') return;
node.getAnimations().forEach(function (animation) {
try { animation.cancel(); } catch (err) {}
});
}
);
}
if (title) {
try {
sanitizeDividerSources(title);
divider = ensureDivider(title);
drop = ensureDropDivider(title);
} catch (err) {}
title.style.opacity = '1';
title.style.borderBottom = '0';
}
if (divider) {
divider.style.background = 'transparent';
Array.prototype.forEach.call(
divider.querySelectorAll('.manifesto-divider-half'),
function (half) {
half.style.transform = 'scaleX(1)';
}
);
}
if (copy) {
copy.style.visibility = 'visible';
Array.prototype.forEach.call(copy.querySelectorAll('.manifesto-line'), function (line) {
line.style.height = LINE_HEIGHT + 'px';
line.style.maxHeight = LINE_HEIGHT + 'px';
line.style.opacity = '1';
line.style.overflow = 'visible';
line.style.clipPath = 'inset(0 0 0 0)';
});
Array.prototype.forEach.call(copy.querySelectorAll('.manifesto-line-inner'), function (inner) {
inner.style.opacity = '1';
inner.style.transform = 'translateY(0)';
});
}
if (drop && divider && copy) {
positionDropAtDivider(drop, divider);
dropDistance = measureDropDistance(copy, divider);
drop.style.opacity = '1';
drop.style.transform = 'translateY(' + dropDistance + 'px)';
}
if (portal) {
portal.classList.remove('manifesto-intro-prepared');
portal.classList.remove('manifesto-intro-running');
portal.classList.add('manifesto-intro-complete');
setState(portal, 'complete');
portal.setAttribute('data-manifesto-intro-played', '1');
}
disarmPending();
}
function prepare(portal) {
var title = portal.querySelector(TITLE_SELECTOR);
var copy = portal.querySelector(COPY_SELECTOR);
var lines;
var divider;
var drop;
var dropGeometry;
if (!title || !copy) return null;
sanitizeDividerSources(title);
try {
lines = buildRenderedLines(copy);
divider = ensureDivider(title);
drop = ensureDropDivider(title);
} catch (error) {
restoreOriginalText(copy);
return null;
}
if (!lines.length || !divider || !drop) {
restoreOriginalText(copy);
return null;
}
applyInitialFrame(portal, title, copy, divider, lines);
drop.style.opacity = '0';
drop.style.transform = 'translateY(0px)';
positionDropAtDivider(drop, divider);
dropGeometry = measureDropGeometry(copy, divider);
return {
title: title,
copy: copy,
divider: divider,
drop: drop,
dropGeometry: dropGeometry,
lines: lines
};
}
function play(portal, prepared) {
var animations = [];
setState(portal, 'running');
portal.setAttribute('data-manifesto-intro-played', '1');
animations.push(animateTitle(prepared.title));
animations.push(animateDivider(prepared.divider));
animations.push(animateDropDivider(
prepared.drop,
prepared.copy,
prepared.dropGeometry,
prepared.lines
));
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-prepared');
portal.classList.remove('manifesto-intro-running');
setState(portal, 'complete');
clearWatchdog(portal);
document.documentElement.classList.remove('manifesto-intro-active');
return true;
});
}
function begin(portal) {
if (!portal || !portal.isConnected) return Promise.resolve(false);
var state = getState(portal);
if (state === 'complete') {
return Promise.resolve(false);
}
if (state === 'preparing' || state === 'running') {
armWatchdog(portal);
return Promise.resolve(false);
}
if (prefersReducedMotion() || typeof Element.prototype.animate !== 'function') {
showImmediately(portal);
return Promise.resolve(false);
}
setState(portal, 'preparing');
armWatchdog(portal);
return waitForFont().then(function () {
if (!portal.isConnected) {
clearWatchdog(portal);
disarmPending();
return false;
}
var 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) return;
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);
});
if (mw && mw.hook) {
mw.hook('wikipage.content').add(function ($content) {
boot($content && $content[0] ? $content[0] : document);
});
}
window.ManifestoIntro = {
version: '20260713-divider-follow-text-004',
init: init,
status: function () {
var portal = document.querySelector(PORTAL_SELECTOR);
return {
loaded: true,
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
};
}
};
}(window, document, window.mw));