(() => { const ANIMATION_LABEL_PATTERN = /^<\d+>$/; const AUTO_SLIDE_INITIAL_DELAY_MS = 1000; const AUTO_SLIDE_INTERVAL_MS = 1500; const LABELS = { "zh": { "play": "播放幻灯片", "pause": "暂停幻灯片", "previous": "上一页", "next": "下一页" }, "zh-hant": { "play": "播放投影片", "pause": "暫停投影片", "previous": "上一頁", "next": "下一頁" }, "en": { "play": "Play slideshow", "pause": "Pause slideshow", "previous": "Previous slide", "next": "Next slide" }, "ru": { "play": "Запустить показ слайдов", "pause": "Приостановить показ слайдов", "previous": "Предыдущий слайд", "next": "Следующий слайд" }, "ja": { "play": "スライドショーを再生", "pause": "スライドショーを一時停止", "previous": "前のスライド", "next": "次のスライド" }, "ko": { "play": "슬라이드 재생", "pause": "슬라이드 일시 정지", "previous": "이전 슬라이드", "next": "다음 슬라이드" } }; // Prefer the full locale, then its parents, and finally English. const LOCALE_ALIASES = { "zh-tw": "zh-hant", "zh-hk": "zh-hant", "zh-mo": "zh-hant" }; const resolveLabels = (language) => { let locale = language.trim().toLowerCase().replaceAll("_", "-"); while (locale) { const candidate = Object.prototype.hasOwnProperty.call(LOCALE_ALIASES, locale) ? LOCALE_ALIASES[locale] : locale; if (Object.prototype.hasOwnProperty.call(LABELS, candidate)) return LABELS[candidate]; const separator = locale.lastIndexOf("-"); locale = separator === -1 ? "" : locale.slice(0, separator); } return LABELS.en; }; const labels = resolveLabels(document.documentElement.lang); const SVG_NS = "http://www.w3.org/2000/svg"; const FA_PLAY_PATH = "M91.2 36.9c-12.4-6.8-27.4-6.5-39.6.7S32 57.9 32 72v368c0 14.1 7.5 27.2 19.6 34.4s27.2 7.5 39.6.7l336-184c12.8-7 20.8-20.5 20.8-35.1s-8-28.1-20.8-35.1z"; const FA_PAUSE_PATH = "M48 32C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h64c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm224 0c-26.5 0-48 21.5-48 48v352c0 26.5 21.5 48 48 48h64c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48z"; const FA_ARROW_LEFT_PATH = "M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 288H480c17.7 0 32-14.3 32-32s-14.3-32-32-32H109.3l105.4-105.4c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"; const FA_ARROW_RIGHT_PATH = "M502.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L402.7 224H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h370.7L297.3 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z"; const initializedSets = new WeakSet(); const controllers = new Set(); const getCheckedIndex = (inputs) => { const checkedIndex = inputs.findIndex((input) => input.checked); return checkedIndex === -1 ? 0 : checkedIndex; }; const setCheckedIndex = (inputs, index) => { const normalizedIndex = ((index % inputs.length) + inputs.length) % inputs.length; inputs[normalizedIndex].checked = true; return normalizedIndex; }; const isAnimationSet = (tabbedSet) => { const labels = Array.from(tabbedSet.querySelectorAll(":scope > .tabbed-labels > label")); if (labels.length < 2) { return false; } return labels.every((label) => ANIMATION_LABEL_PATTERN.test(label.textContent.trim())); }; const createSvgIcon = ({ className, path, viewBox, width, height }) => { const icon = document.createElementNS(SVG_NS, "svg"); const iconPath = document.createElementNS(SVG_NS, "path"); icon.setAttribute("class", className); icon.setAttribute("aria-hidden", "true"); icon.setAttribute("viewBox", viewBox); icon.setAttribute("focusable", "false"); if (width) { icon.setAttribute("width", width); } if (height) { icon.setAttribute("height", height); } iconPath.setAttribute("d", path); icon.append(iconPath); return { icon, iconPath }; }; const createIconButton = ({ className, ariaLabel, path }) => { const button = document.createElement("button"); const { icon } = createSvgIcon({ className: "animation-controls__nav-icon", path, viewBox: "0 0 512 512", }); button.type = "button"; button.className = `animation-controls__button ${className}`; button.setAttribute("aria-label", ariaLabel); button.append(icon); return button; }; const createPlayButton = () => { const button = document.createElement("button"); const glyph = document.createElement("span"); const { icon, iconPath } = createSvgIcon({ className: "animation-controls__play-icon", path: FA_PLAY_PATH, viewBox: "0 0 448 512", width: "12", height: "12", }); const srOnly = document.createElement("span"); button.type = "button"; button.className = "animation-controls__button animation-controls__play"; button.setAttribute("aria-label", labels.play); glyph.className = "animation-controls__play-glyph"; icon.setAttribute("preserveAspectRatio", "xMidYMid meet"); glyph.append(icon); srOnly.className = "animation-controls__sr-only"; srOnly.textContent = labels.play; button.append(glyph, srOnly); return { button, srOnly, icon, iconPath }; }; const initAnimationSet = (tabbedSet) => { if (initializedSets.has(tabbedSet) || !isAnimationSet(tabbedSet)) { return; } const inputs = Array.from(tabbedSet.querySelectorAll(':scope > input[type="radio"]')); const tabbedContent = tabbedSet.querySelector(":scope > .tabbed-content"); if (inputs.length < 2 || !tabbedContent) { return; } initializedSets.add(tabbedSet); tabbedSet.dataset.autoSlide = "true"; const controls = document.createElement("div"); controls.className = "animation-controls"; const playControl = createPlayButton(); const playButton = playControl.button; const nav = document.createElement("div"); nav.className = "animation-controls__nav"; const prevButton = createIconButton({ className: "animation-controls__prev", ariaLabel: labels.previous, path: FA_ARROW_LEFT_PATH, }); const pageIndicator = document.createElement("span"); pageIndicator.className = "animation-controls__page"; pageIndicator.setAttribute("aria-live", "polite"); const nextButton = createIconButton({ className: "animation-controls__next", ariaLabel: labels.next, path: FA_ARROW_RIGHT_PATH, }); nav.append(prevButton, pageIndicator, nextButton); controls.append(playButton, nav); tabbedContent.insertAdjacentElement("afterend", controls); const state = { inputs, currentIndex: getCheckedIndex(inputs), intervalId: null, timeoutId: null, isPlaying: false, }; const updatePlayButton = () => { const label = state.isPlaying ? labels.pause : labels.play; playButton.setAttribute("aria-label", label); playButton.classList.toggle("is-playing", state.isPlaying); playControl.srOnly.textContent = label; playControl.icon.setAttribute("viewBox", state.isPlaying ? "0 0 384 512" : "0 0 448 512"); playControl.icon.setAttribute("width", state.isPlaying ? "10" : "12"); playControl.iconPath.setAttribute("d", state.isPlaying ? FA_PAUSE_PATH : FA_PLAY_PATH); }; const updatePageIndicator = () => { pageIndicator.textContent = `${state.currentIndex + 1} / ${inputs.length}`; }; const stop = () => { if (state.timeoutId !== null) { window.clearTimeout(state.timeoutId); state.timeoutId = null; } if (state.intervalId !== null) { window.clearInterval(state.intervalId); state.intervalId = null; } state.isPlaying = false; updatePlayButton(); }; const syncCurrentIndex = () => { state.currentIndex = getCheckedIndex(inputs); }; const moveTo = (index) => { state.currentIndex = setCheckedIndex(inputs, index); updatePageIndicator(); }; const step = (delta) => { moveTo(state.currentIndex + delta); }; const start = () => { if (state.isPlaying) { return; } state.isPlaying = true; updatePlayButton(); state.timeoutId = window.setTimeout(() => { step(1); state.timeoutId = null; state.intervalId = window.setInterval(() => { step(1); }, AUTO_SLIDE_INTERVAL_MS); }, AUTO_SLIDE_INITIAL_DELAY_MS); }; playButton.addEventListener("click", () => { if (state.isPlaying) { stop(); return; } syncCurrentIndex(); start(); }); prevButton.addEventListener("click", () => { syncCurrentIndex(); step(-1); }); nextButton.addEventListener("click", () => { syncCurrentIndex(); step(1); }); inputs.forEach((input, index) => { input.addEventListener("change", () => { if (input.checked) { state.currentIndex = index; updatePageIndicator(); } }); }); controllers.add(stop); updatePlayButton(); updatePageIndicator(); }; const initAutoSlide = () => { document.querySelectorAll(".tabbed-set").forEach(initAnimationSet); }; document.addEventListener("visibilitychange", () => { if (!document.hidden) { return; } controllers.forEach((stop) => stop()); }); if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initAutoSlide, { once: true }); } else { initAutoSlide(); } })(); /*! update cache: 20261103165728 */