Skip to content

Commit 913bcb0

Browse files
committed
f
1 parent 889c2aa commit 913bcb0

3 files changed

Lines changed: 231 additions & 0 deletions

File tree

book.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ additional-js = [
2121
"theme/pagetoc.js",
2222
"theme/ht_searcher.js",
2323
"theme/sponsor.js",
24+
"theme/motion.js",
2425
"theme/ai.js"
2526
]
2627
no-section-label = true

theme/css/general.css

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,49 @@ body {
7070
animation: float-particle 16s infinite;
7171
}
7272

73+
html.motion-paused .bg-animation,
74+
html.motion-paused .grid-overlay,
75+
html.motion-paused .particle,
76+
html.motion-paused #ht-ai-btn {
77+
animation-play-state: paused !important;
78+
}
79+
80+
html.motion-reduced .bg-animation,
81+
html.motion-reduced .grid-overlay,
82+
html.motion-reduced .particles {
83+
display: none !important;
84+
}
85+
86+
html.motion-reduced .particle,
87+
html.motion-reduced #ht-ai-btn {
88+
animation: none !important;
89+
}
90+
91+
html.motion-reduced #ht-ai-btn {
92+
background: linear-gradient(45deg, #b31328, #d42d3f) !important;
93+
background-size: auto !important;
94+
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.3) !important;
95+
}
96+
97+
@media (prefers-reduced-motion: reduce) {
98+
.bg-animation,
99+
.grid-overlay,
100+
.particles {
101+
display: none !important;
102+
}
103+
104+
.particle,
105+
#ht-ai-btn {
106+
animation: none !important;
107+
}
108+
109+
#ht-ai-btn {
110+
background: linear-gradient(45deg, #b31328, #d42d3f) !important;
111+
background-size: auto !important;
112+
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.3) !important;
113+
}
114+
}
115+
73116
@keyframes bg-drift {
74117
0% { transform: translate3d(0, 0, 0) scale(1); }
75118
100% { transform: translate3d(-2%, 1%, 0) scale(1.04); }

theme/motion.js

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
(function () {
2+
var docEl = document.documentElement;
3+
var REDUCED_CLASS = "motion-reduced";
4+
var PAUSED_CLASS = "motion-paused";
5+
var motionState = { mode: "normal", reason: "default" };
6+
7+
function setMode(mode, reason) {
8+
var nextMode = mode === "reduced" ? "reduced" : "normal";
9+
if (motionState.mode === nextMode && motionState.reason === reason) {
10+
return;
11+
}
12+
13+
motionState.mode = nextMode;
14+
motionState.reason = reason || motionState.reason;
15+
docEl.classList.toggle(REDUCED_CLASS, nextMode === "reduced");
16+
docEl.dataset.motionMode = nextMode;
17+
window.__hacktricksMotion = {
18+
mode: motionState.mode,
19+
reason: motionState.reason,
20+
shouldReduceMotion: function () {
21+
return motionState.mode === "reduced";
22+
}
23+
};
24+
25+
document.dispatchEvent(new CustomEvent("hacktricks:motionchange", {
26+
detail: {
27+
mode: motionState.mode,
28+
reason: motionState.reason
29+
}
30+
}));
31+
}
32+
33+
function updateVisibilityState() {
34+
docEl.classList.toggle(PAUSED_CLASS, document.visibilityState === "hidden");
35+
}
36+
37+
function getMediaQuery() {
38+
if (typeof window.matchMedia !== "function") {
39+
return null;
40+
}
41+
return window.matchMedia("(prefers-reduced-motion: reduce)");
42+
}
43+
44+
function getDeviceHints() {
45+
var hints = {
46+
lowCapability: false,
47+
hardwareConcurrency: null,
48+
deviceMemory: null
49+
};
50+
51+
if (typeof navigator.hardwareConcurrency === "number") {
52+
hints.hardwareConcurrency = navigator.hardwareConcurrency;
53+
}
54+
55+
if (typeof navigator.deviceMemory === "number") {
56+
hints.deviceMemory = navigator.deviceMemory;
57+
}
58+
59+
hints.lowCapability =
60+
(hints.hardwareConcurrency !== null && hints.hardwareConcurrency <= 4) ||
61+
(hints.deviceMemory !== null && hints.deviceMemory <= 4);
62+
63+
return hints;
64+
}
65+
66+
function monitorAnimationHealth() {
67+
if (
68+
document.visibilityState === "hidden" ||
69+
typeof window.requestAnimationFrame !== "function"
70+
) {
71+
return;
72+
}
73+
74+
var mediaQuery = getMediaQuery();
75+
if (mediaQuery && mediaQuery.matches) {
76+
setMode("reduced", "prefers-reduced-motion");
77+
return;
78+
}
79+
80+
var hints = getDeviceHints();
81+
var perfState = {
82+
frameCount: 0,
83+
firstFrameAt: 0,
84+
longTaskTime: 0,
85+
worstFrameGap: 0
86+
};
87+
var observer = null;
88+
89+
if (typeof PerformanceObserver === "function") {
90+
try {
91+
observer = new PerformanceObserver(function (list) {
92+
list.getEntries().forEach(function (entry) {
93+
perfState.longTaskTime += entry.duration;
94+
});
95+
});
96+
observer.observe({ type: "longtask", buffered: true });
97+
} catch (error) {
98+
observer = null;
99+
}
100+
}
101+
102+
var targetDuration = 2500;
103+
var fpsThreshold = hints.lowCapability ? 50 : 42;
104+
var longTaskThreshold = hints.lowCapability ? 160 : 240;
105+
var frameGapThreshold = hints.lowCapability ? 90 : 120;
106+
var lastFrameAt = 0;
107+
108+
function finish(now) {
109+
if (observer) {
110+
observer.disconnect();
111+
}
112+
113+
var elapsed = now - perfState.firstFrameAt;
114+
if (elapsed <= 0) {
115+
return;
116+
}
117+
118+
var fps = (perfState.frameCount * 1000) / elapsed;
119+
var shouldReduce =
120+
fps < fpsThreshold ||
121+
perfState.longTaskTime > longTaskThreshold ||
122+
perfState.worstFrameGap > frameGapThreshold;
123+
124+
if (shouldReduce) {
125+
setMode("reduced", "runtime-performance");
126+
}
127+
}
128+
129+
function sample(now) {
130+
if (!perfState.firstFrameAt) {
131+
perfState.firstFrameAt = now;
132+
}
133+
134+
perfState.frameCount += 1;
135+
if (lastFrameAt) {
136+
perfState.worstFrameGap = Math.max(
137+
perfState.worstFrameGap,
138+
now - lastFrameAt
139+
);
140+
}
141+
lastFrameAt = now;
142+
143+
if (now - perfState.firstFrameAt >= targetDuration) {
144+
finish(now);
145+
return;
146+
}
147+
148+
window.requestAnimationFrame(sample);
149+
}
150+
151+
window.requestAnimationFrame(sample);
152+
}
153+
154+
var mediaQuery = getMediaQuery();
155+
if (mediaQuery && mediaQuery.matches) {
156+
setMode("reduced", "prefers-reduced-motion");
157+
} else {
158+
setMode("normal", "default");
159+
if (document.readyState === "complete") {
160+
window.setTimeout(monitorAnimationHealth, 1200);
161+
} else {
162+
window.addEventListener("load", function () {
163+
window.setTimeout(monitorAnimationHealth, 1200);
164+
}, { once: true });
165+
}
166+
}
167+
168+
if (mediaQuery) {
169+
var handlePreferenceChange = function (event) {
170+
if (event.matches) {
171+
setMode("reduced", "prefers-reduced-motion");
172+
} else if (motionState.reason === "prefers-reduced-motion") {
173+
setMode("normal", "preference-restored");
174+
window.setTimeout(monitorAnimationHealth, 600);
175+
}
176+
};
177+
178+
if (typeof mediaQuery.addEventListener === "function") {
179+
mediaQuery.addEventListener("change", handlePreferenceChange);
180+
} else if (typeof mediaQuery.addListener === "function") {
181+
mediaQuery.addListener(handlePreferenceChange);
182+
}
183+
}
184+
185+
updateVisibilityState();
186+
document.addEventListener("visibilitychange", updateVisibilityState);
187+
})();

0 commit comments

Comments
 (0)