Files
hacktricks-cloud/theme/motion.js
Carlos Polop 913bcb0f09 f
2026-04-19 12:54:23 +02:00

188 lines
5.8 KiB
JavaScript

(function () {
var docEl = document.documentElement;
var REDUCED_CLASS = "motion-reduced";
var PAUSED_CLASS = "motion-paused";
var motionState = { mode: "normal", reason: "default" };
function setMode(mode, reason) {
var nextMode = mode === "reduced" ? "reduced" : "normal";
if (motionState.mode === nextMode && motionState.reason === reason) {
return;
}
motionState.mode = nextMode;
motionState.reason = reason || motionState.reason;
docEl.classList.toggle(REDUCED_CLASS, nextMode === "reduced");
docEl.dataset.motionMode = nextMode;
window.__hacktricksMotion = {
mode: motionState.mode,
reason: motionState.reason,
shouldReduceMotion: function () {
return motionState.mode === "reduced";
}
};
document.dispatchEvent(new CustomEvent("hacktricks:motionchange", {
detail: {
mode: motionState.mode,
reason: motionState.reason
}
}));
}
function updateVisibilityState() {
docEl.classList.toggle(PAUSED_CLASS, document.visibilityState === "hidden");
}
function getMediaQuery() {
if (typeof window.matchMedia !== "function") {
return null;
}
return window.matchMedia("(prefers-reduced-motion: reduce)");
}
function getDeviceHints() {
var hints = {
lowCapability: false,
hardwareConcurrency: null,
deviceMemory: null
};
if (typeof navigator.hardwareConcurrency === "number") {
hints.hardwareConcurrency = navigator.hardwareConcurrency;
}
if (typeof navigator.deviceMemory === "number") {
hints.deviceMemory = navigator.deviceMemory;
}
hints.lowCapability =
(hints.hardwareConcurrency !== null && hints.hardwareConcurrency <= 4) ||
(hints.deviceMemory !== null && hints.deviceMemory <= 4);
return hints;
}
function monitorAnimationHealth() {
if (
document.visibilityState === "hidden" ||
typeof window.requestAnimationFrame !== "function"
) {
return;
}
var mediaQuery = getMediaQuery();
if (mediaQuery && mediaQuery.matches) {
setMode("reduced", "prefers-reduced-motion");
return;
}
var hints = getDeviceHints();
var perfState = {
frameCount: 0,
firstFrameAt: 0,
longTaskTime: 0,
worstFrameGap: 0
};
var observer = null;
if (typeof PerformanceObserver === "function") {
try {
observer = new PerformanceObserver(function (list) {
list.getEntries().forEach(function (entry) {
perfState.longTaskTime += entry.duration;
});
});
observer.observe({ type: "longtask", buffered: true });
} catch (error) {
observer = null;
}
}
var targetDuration = 2500;
var fpsThreshold = hints.lowCapability ? 50 : 42;
var longTaskThreshold = hints.lowCapability ? 160 : 240;
var frameGapThreshold = hints.lowCapability ? 90 : 120;
var lastFrameAt = 0;
function finish(now) {
if (observer) {
observer.disconnect();
}
var elapsed = now - perfState.firstFrameAt;
if (elapsed <= 0) {
return;
}
var fps = (perfState.frameCount * 1000) / elapsed;
var shouldReduce =
fps < fpsThreshold ||
perfState.longTaskTime > longTaskThreshold ||
perfState.worstFrameGap > frameGapThreshold;
if (shouldReduce) {
setMode("reduced", "runtime-performance");
}
}
function sample(now) {
if (!perfState.firstFrameAt) {
perfState.firstFrameAt = now;
}
perfState.frameCount += 1;
if (lastFrameAt) {
perfState.worstFrameGap = Math.max(
perfState.worstFrameGap,
now - lastFrameAt
);
}
lastFrameAt = now;
if (now - perfState.firstFrameAt >= targetDuration) {
finish(now);
return;
}
window.requestAnimationFrame(sample);
}
window.requestAnimationFrame(sample);
}
var mediaQuery = getMediaQuery();
if (mediaQuery && mediaQuery.matches) {
setMode("reduced", "prefers-reduced-motion");
} else {
setMode("normal", "default");
if (document.readyState === "complete") {
window.setTimeout(monitorAnimationHealth, 1200);
} else {
window.addEventListener("load", function () {
window.setTimeout(monitorAnimationHealth, 1200);
}, { once: true });
}
}
if (mediaQuery) {
var handlePreferenceChange = function (event) {
if (event.matches) {
setMode("reduced", "prefers-reduced-motion");
} else if (motionState.reason === "prefers-reduced-motion") {
setMode("normal", "preference-restored");
window.setTimeout(monitorAnimationHealth, 600);
}
};
if (typeof mediaQuery.addEventListener === "function") {
mediaQuery.addEventListener("change", handlePreferenceChange);
} else if (typeof mediaQuery.addListener === "function") {
mediaQuery.addListener(handlePreferenceChange);
}
}
updateVisibilityState();
document.addEventListener("visibilitychange", updateVisibilityState);
})();