PageSourceSearch

https://www.bjhongshantech.com/assets/home-secondary-motion.js?v=20260807-secondary-motion1

js bjhongshantech.com collected 2026-10-10 00:35:16 UTC 8,065 bytes, 241 lines download raw bytes

1(() => {
2  const desktopView = window.matchMedia("(min-width: 961px)");
3  const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
4  const easing = "cubic-bezier(0.22, 1, 0.36, 1)";
5  const revealedSections = new WeakSet();
6  const activeAnimations = new Set();
7  let observer = null;
8  let initialized = false;
9
10  const play = (element, keyframes, options = {}) => {
11    if (!element || typeof element.animate !== "function") return null;
12
13    const animation = element.animate(keyframes, {
14      duration: 520,
15      delay: 0,
16      easing,
17      fill: "both",
18      ...options,
19    });
20
21    activeAnimations.add(animation);
22    animation.finished
23      .catch(() => {})
24      .finally(() => {
25        activeAnimations.delete(animation);
26        animation.cancel();
27      });
28    return animation;
29  };
30
31  const fadeUp = (element, delay = 0, distance = 16, duration = 520) =>
32    play(
33      element,
34      [
35        { opacity: 0, transform: `translate3d(0, ${distance}px, 0)` },
36        { opacity: 1, transform: "translate3d(0, 0, 0)" },
37      ],
38      { delay, duration },
39    );
40
41  const fadeSide = (element, direction, delay = 0, distance = 22, duration = 560) =>
42    play(
43      element,
44      [
45        { opacity: 0, transform: `translate3d(${direction * distance}px, 0, 0)` },
46        { opacity: 1, transform: "translate3d(0, 0, 0)" },
47      ],
48      { delay, duration },
49    );
50
51  const revealCases = (section) => {
52    fadeSide(section.querySelector(".section-heading"), 1, 0, 20, 540);
53    fadeSide(section.querySelector(".case-featured"), -1, 100, 24, 620);
54
55    section.querySelectorAll(".case-supporting > article").forEach((card, index) => {
56      fadeSide(card, 1, 170 + index * 90, 20, 560);
57    });
58    fadeSide(section.querySelector(".case-heading-more"), 1, 350, 18, 520);
59  };
60
61  const revealScenes = (section) => {
62    fadeUp(section.querySelector(".section-heading"), 0, 14, 500);
63    section.querySelectorAll(".scene-panorama > a").forEach((card, index) => {
64      fadeUp(card, 110 + index * 65, 18, 540);
65    });
66    fadeUp(section.querySelector(".scene-more-links"), 420, 12, 460);
67  };
68
69  const revealProcess = (section) => {
70    fadeUp(section.querySelector(".section-heading"), 0, 14, 500);
71
72    section.querySelectorAll(".delivery-journey > li").forEach((step, index) => {
73      const delay = 90 + index * 100;
74      play(
75        step.querySelector(":scope > span"),
76        [
77          { opacity: 0.2, filter: "brightness(0.7)", transform: "scale(0.82)" },
78          { opacity: 1, filter: "brightness(1.2)", transform: "scale(1.045)", offset: 0.72 },
79          { opacity: 1, filter: "brightness(1)", transform: "scale(1)" },
80        ],
81        { delay, duration: 520 },
82      );
83      fadeUp(step.querySelector("h3"), delay + 90, 8, 400);
84      fadeUp(step.querySelector("p"), delay + 130, 7, 400);
85      play(
86        step.querySelector(".process-step-arrow"),
87        [
88          { opacity: 0, transform: "translate3d(-8px, 0, 0) scaleX(0.72)" },
89          { opacity: 1, transform: "translate3d(0, 0, 0) scaleX(1)" },
90        ],
91        { delay: delay + 110, duration: 460 },
92      );
93    });
94
95    const evidence = section.querySelector(".delivery-evidence");
96    if (!evidence) return;
97    const figures = evidence.querySelectorAll(":scope > figure");
98    fadeSide(figures[0], -1, 880, 18, 520);
99    fadeUp(evidence.querySelector(":scope > span"), 980, 10, 420);
100    fadeSide(figures[1], 1, 1060, 18, 520);
101    fadeSide(evidence.querySelector(":scope > aside"), 1, 1160, 16, 500);
102  };
103
104  const revealKnowledge = (section) => {
105    fadeUp(section.querySelector(".section-heading"), 0, 14, 500);
106    section.querySelectorAll(".project-start-paths > article").forEach((card, index) => {
107      fadeUp(card, 110 + index * 80, 16, 540);
108    });
109    fadeSide(section.querySelector(".project-consult-card"), 1, 260, 18, 560);
110    fadeUp(section.querySelector(".project-assurances"), 410, 12, 500);
111  };
112
113  const revealFaq = (section) => {
114    fadeUp(section.querySelector(".section-heading"), 0, 14, 500);
115    fadeSide(section.querySelector(".faq-list"), -1, 120, 18, 560);
116    fadeSide(section.querySelector(".faq-expert-card"), 1, 220, 18, 560);
117  };
118
119  const revealContact = (section) => {
120    fadeSide(section.querySelector(".home-cta-copy"), -1, 0, 24, 560);
121    fadeSide(section.querySelector(".home-cta-media"), 1, 100, 24, 620);
122  };
123
124  const revealers = new Map([
125    ["cases", revealCases],
126    ["home-scenes", revealScenes],
127    ["home-process", revealProcess],
128    ["home-knowledge", revealKnowledge],
129    ["home-faq", revealFaq],
130    ["home-contact", revealContact],
131  ]);
132
133  const revealSection = (section) => {
134    if (!section || revealedSections.has(section)) return;
135    revealedSections.add(section);
136    section.classList.add("secondary-motion-revealed");
137    revealers.get(section.id)?.(section);
138    observer?.unobserve(section);
139  };
140
141  const setupFaqMotion = () => {
142    document.querySelectorAll("#home-faq .faq-list details").forEach((details) => {
143      const summary = details.querySelector("summary");
144      const panel = details.querySelector(":scope > div");
145      if (!summary || !panel || typeof panel.animate !== "function" || summary.dataset.motionReady) return;
146
147      summary.dataset.motionReady = "true";
148      let panelAnimation = null;
149
150      summary.addEventListener("click", (event) => {
151        if (!desktopView.matches || reduceMotion.matches) return;
152        event.preventDefault();
153        panelAnimation?.cancel();
154
155        if (details.open) {
156          const startHeight = panel.getBoundingClientRect().height;
157          panelAnimation = panel.animate(
158            [
159              { height: `${startHeight}px`, opacity: 1, transform: "translate3d(0, 0, 0)" },
160              { height: "0px", opacity: 0, transform: "translate3d(0, -6px, 0)" },
161            ],
162            { duration: 220, easing, fill: "both" },
163          );
164          panelAnimation.finished
165            .then(() => {
166              details.open = false;
167              panelAnimation.cancel();
168              panelAnimation = null;
169            })
170            .catch(() => {});
171          return;
172        }
173
174        details.open = true;
175        const endHeight = panel.scrollHeight;
176        panelAnimation = panel.animate(
177          [
178            { height: "0px", opacity: 0, transform: "translate3d(0, -6px, 0)" },
179            { height: `${endHeight}px`, opacity: 1, transform: "translate3d(0, 0, 0)" },
180          ],
181          { duration: 260, easing, fill: "both" },
182        );
183        panelAnimation.finished
184          .then(() => {
185            panelAnimation.cancel();
186            panelAnimation = null;
187          })
188          .catch(() => {});
189      });
190    });
191  };
192
193  const stopMotion = () => {
194    observer?.disconnect();
195    observer = null;
196    activeAnimations.forEach((animation) => animation.cancel());
197    activeAnimations.clear();
198    initialized = false;
199  };
200
201  const init = () => {
202    if (initialized || !desktopView.matches || reduceMotion.matches) return;
203    initialized = true;
204    document.body.classList.add("secondary-motion-enabled");
205    setupFaqMotion();
206
207    const sections = Array.from(revealers.keys())
208      .map((id) => document.getElementById(id))
209      .filter(Boolean);
210
211    if (!("IntersectionObserver" in window)) {
212      sections.forEach(revealSection);
213      return;
214    }
215
216    observer = new IntersectionObserver(
217      (entries) => {
218        entries.forEach((entry) => {
219          if (entry.isIntersecting && entry.intersectionRatio >= 0.14) {
220            revealSection(entry.target);
221          }
222        });
223      },
224      { threshold: [0, 0.14, 0.28], rootMargin: "0px 0px -10% 0px" },
225    );
226    sections.forEach((section) => {
227      if (!revealedSections.has(section)) observer.observe(section);
228    });
229  };
230
231  desktopView.addEventListener?.("change", (event) => {
232    if (event.matches) init();
233    else stopMotion();
234  });
235  reduceMotion.addEventListener?.("change", (event) => {
236    if (event.matches) stopMotion();
237    else init();
238  });
239
240  init();
241})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.