Files
谢宇宁andClaude Opus 5.5 0fbdba5ccf Initial commit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 11:47:47 +07:00

338 lines
9.2 KiB
JavaScript

(function () {
var root = document.querySelector("[data-hero]");
if (!root) return;
var slides = Array.prototype.slice.call(root.querySelectorAll("[data-hero-slide]"));
var thumbs = Array.prototype.slice.call(root.querySelectorAll("[data-hero-index]"));
var currentEl = root.querySelector("[data-hero-current]");
var fillEl = root.querySelector(".hero__pager-fill");
var bgEl = root.querySelector("[data-hero-bg]");
var total = slides.length;
if (!total) return;
var index = 0;
var timer = null;
var interval = 3000;
var bgToken = 0;
var touchStartX = null;
var touchStartY = null;
var mouseStartX = null;
var mouseStartY = null;
var mouseDown = false;
var suppressClickUntil = 0;
var posterPreloadTimer = null;
var bgLayers = [];
var activeBgIndex = 0;
if (bgEl) {
var alternateBg = bgEl.cloneNode(false);
alternateBg.classList.remove("is-active", "lazy");
alternateBg.removeAttribute("data-hero-bg");
alternateBg.removeAttribute("data-src");
alternateBg.removeAttribute("data-lazy-eager");
alternateBg.removeAttribute("data-decrypted");
alternateBg.removeAttribute("data-decrypt-failed");
alternateBg.removeAttribute("fetchpriority");
alternateBg.src = "/static/images/placeholder.png?v=2";
bgEl.parentNode.insertBefore(alternateBg, bgEl.nextSibling);
bgLayers = [bgEl, alternateBg];
}
// 初始 HTML 只展示首张;脚本就绪后改为叠层,由 CSS 完成平滑交叉过渡。
slides.forEach(function (slide, i) {
slide.removeAttribute("hidden");
slide.setAttribute("aria-hidden", i === index ? "false" : "true");
});
function pad(n) {
return n < 10 ? "0" + n : String(n);
}
function slideBgSrc(slide) {
var media = slide.querySelector("[data-hero-media]");
if (!media) return "";
return media.getAttribute("data-hero-media") || media.getAttribute("data-src") || "";
}
function loadSlidePoster(slideIndex) {
var slide = slides[slideIndex];
if (!slide) return;
var poster = slide.querySelector("[data-hero-poster]");
if (!poster || poster.getAttribute("data-hero-loaded") === "1" || poster.getAttribute("data-hero-loading") === "1") {
return;
}
var src = poster.getAttribute("data-src") || "";
if (!src) return;
poster.setAttribute("data-hero-loading", "1");
function done() {
poster.removeAttribute("data-hero-loading");
poster.setAttribute("data-hero-loaded", "1");
}
if (typeof window.processImage === "function") {
window.processImage(src, poster).then(done);
return;
}
poster.addEventListener("load", done, { once: true });
poster.addEventListener("error", done, { once: true });
poster.src = src;
}
function queueNextPoster() {
if (posterPreloadTimer) {
window.clearTimeout(posterPreloadTimer);
}
if (total < 2) return;
posterPreloadTimer = window.setTimeout(function () {
loadSlidePoster((index + 1) % total);
}, 600);
}
function syncBg(slide) {
if (!bgLayers.length) return;
var src = slideBgSrc(slide);
if (!src) return;
var currentBg = bgLayers[activeBgIndex];
var nextBgIndex = activeBgIndex === 0 ? 1 : 0;
var targetBg = bgLayers[nextBgIndex];
if (currentBg.getAttribute("data-src") === src) {
targetBg = currentBg;
nextBgIndex = activeBgIndex;
}
targetBg.setAttribute("data-src", src);
targetBg.removeAttribute("data-decrypted");
targetBg.removeAttribute("data-decrypt-failed");
var token = ++bgToken;
function reveal(result) {
if (token !== bgToken) return;
if (result) {
targetBg.setAttribute("data-decrypted", "1");
}
if (nextBgIndex === activeBgIndex) return;
window.requestAnimationFrame(function () {
if (token !== bgToken) return;
targetBg.classList.add("is-active");
currentBg.classList.remove("is-active");
activeBgIndex = nextBgIndex;
});
}
if (typeof window.processImage === "function") {
// 必须走解密;禁止把加密原址直接赋给 src
window.processImage(src, targetBg).then(reveal);
return;
}
targetBg.src = src;
reveal(src);
}
function goTo(next) {
var nextIndex = ((next % total) + total) % total;
if (nextIndex === index) return;
index = nextIndex;
loadSlidePoster(index);
queueNextPoster();
slides.forEach(function (slide, i) {
var active = i === index;
slide.classList.toggle("is-active", active);
slide.setAttribute("aria-hidden", active ? "false" : "true");
if (active) {
syncBg(slide);
}
});
thumbs.forEach(function (thumb, i) {
var active = i === index;
thumb.classList.toggle("is-active", active);
thumb.setAttribute("aria-selected", active ? "true" : "false");
});
if (currentEl) currentEl.textContent = pad(index + 1);
if (fillEl) fillEl.style.width = ((index + 1) / total) * 100 + "%";
}
function next() {
goTo(index + 1);
}
function previous() {
goTo(index - 1);
}
function start() {
stop();
if (total < 2 || mouseDown || touchStartX !== null) return;
timer = window.setInterval(next, interval);
}
function stop() {
if (timer) {
window.clearInterval(timer);
timer = null;
}
}
thumbs.forEach(function (thumb) {
var thumbIndex = Number(thumb.getAttribute("data-hero-index"));
function preloadThumbSlide() {
if (!Number.isNaN(thumbIndex)) {
loadSlidePoster(thumbIndex);
}
}
thumb.addEventListener("mouseenter", preloadThumbSlide);
thumb.addEventListener("focus", preloadThumbSlide);
thumb.addEventListener("touchstart", preloadThumbSlide, { passive: true });
thumb.addEventListener("click", function () {
if (Number.isNaN(thumbIndex)) return;
goTo(thumbIndex);
start();
});
});
var canHover = !window.matchMedia || window.matchMedia("(hover: hover)").matches;
if (canHover) {
root.addEventListener("mouseenter", stop);
root.addEventListener("mouseleave", start);
}
root.addEventListener("dragstart", function (event) {
event.preventDefault();
});
root.addEventListener("mousedown", function (event) {
if (event.button !== 0) return;
mouseStartX = event.clientX;
mouseStartY = event.clientY;
mouseDown = true;
root.classList.add("is-dragging");
stop();
});
window.addEventListener("mousemove", function (event) {
if (!mouseDown || mouseStartX === null || mouseStartY === null) return;
var deltaX = event.clientX - mouseStartX;
var deltaY = event.clientY - mouseStartY;
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) >= 6) {
event.preventDefault();
}
});
window.addEventListener("mouseup", function (event) {
if (!mouseDown || mouseStartX === null || mouseStartY === null) return;
var deltaX = event.clientX - mouseStartX;
var deltaY = event.clientY - mouseStartY;
var isHorizontalSwipe = Math.abs(deltaX) >= 50 && Math.abs(deltaX) > Math.abs(deltaY) * 1.2;
mouseStartX = null;
mouseStartY = null;
mouseDown = false;
root.classList.remove("is-dragging");
if (isHorizontalSwipe) {
suppressClickUntil = Date.now() + 400;
if (deltaX < 0) {
next();
} else {
previous();
}
}
start();
});
window.addEventListener("blur", function () {
if (!mouseDown) return;
mouseStartX = null;
mouseStartY = null;
mouseDown = false;
root.classList.remove("is-dragging");
start();
});
root.addEventListener(
"touchstart",
function (event) {
if (event.touches.length !== 1) return;
touchStartX = event.touches[0].clientX;
touchStartY = event.touches[0].clientY;
stop();
},
{ passive: true }
);
root.addEventListener(
"touchend",
function (event) {
if (touchStartX === null || touchStartY === null || !event.changedTouches.length) {
start();
return;
}
var touch = event.changedTouches[0];
var deltaX = touch.clientX - touchStartX;
var deltaY = touch.clientY - touchStartY;
var isHorizontalSwipe = Math.abs(deltaX) >= 50 && Math.abs(deltaX) > Math.abs(deltaY) * 1.2;
touchStartX = null;
touchStartY = null;
if (isHorizontalSwipe) {
event.preventDefault();
suppressClickUntil = Date.now() + 400;
if (deltaX < 0) {
next();
} else {
previous();
}
}
start();
},
{ passive: false }
);
root.addEventListener(
"touchcancel",
function () {
touchStartX = null;
touchStartY = null;
start();
},
{ passive: true }
);
root.addEventListener(
"click",
function (event) {
if (Date.now() >= suppressClickUntil) return;
event.preventDefault();
event.stopPropagation();
},
true
);
document.addEventListener("visibilitychange", function () {
if (document.hidden) {
stop();
} else {
start();
}
});
if (fillEl) fillEl.style.width = (1 / total) * 100 + "%";
loadSlidePoster(index);
queueNextPoster();
// 首帧也走解密;与 imgDecypt 的 eager 加载可共享 decryptedURLCache
syncBg(slides[0]);
start();
})();