PageSourceSearch

https://hifiman.com/strict/home-fixes.js?v=20261001-1

js hifiman.com collected 2026-10-02 09:53:56 UTC 27,965 bytes, 437 lines download raw bytes

1(() => {
2  const cn = document.documentElement.lang.toLowerCase().startsWith("zh") || location.hostname === "cn.hifiman.net.cn" || location.hostname === "hifiman.cn" || location.hostname.endsWith(".hifiman.cn") || location.hostname.startsWith("cn.localhost");
3  const safeHref = (href) => {
4    if (typeof href !== "string") return null;
5    if (href.startsWith("/") && !href.startsWith("//")) return href;
6    try {
7      const url = new URL(href);
8      return url.protocol === "https:" || location.hostname.endsWith(".localhost") && url.protocol === "http:" && url.hostname === `files${cn ? "cn" : "com"}.hifiman.localhost` ? url.href : null;
9    } catch { return null; }
10  };
11  const eventWebsite = value => {
12    try { const url = new URL(value); return ["https:", "http:"].includes(url.protocol) ? url.href : null; }
13    catch { return null; }
14  };
15  const route = (a, href) => { const safe = safeHref(href); if (a && safe) a.setAttribute("href", safe); };
16  const cardImage = (card, item) => {
17    const image = card.querySelector("img");
18    if (!image) return;
19    card.removeAttribute("data-content-loading");
20    card.removeAttribute("data-image-ready");
21    card.setAttribute("data-image-managed", "");
22    image.removeAttribute("src");
23    image.removeAttribute("srcset");
24    image.loading = "lazy"; image.decoding = "async"; image.alt = "";
25    const url = safeHref(item.image_url) || (/^http:\/\/files(?:cn|com)\.hifiman\.localhost(?::\d+)?\//i.test(item.image_url || "") ? item.image_url : null);
26    if (!url) return;
27    image.onload = () => card.setAttribute("data-image-ready", "");
28    image.onerror = () => card.removeAttribute("data-image-ready");
29    image.sizes = "(max-width: 760px) 78vw, (max-width: 1100px) 40vw, 25vw";
30    if (item.image_srcset) image.srcset = item.image_srcset;
31    image.src = url;
32    if (image.complete && image.naturalWidth) image.onload();
33  };
34  document.querySelectorAll('a[href="#"]').forEach((a) => {
35    const text = a.textContent.trim();
36    route(a, /商店|Store/i.test(text) ? (cn ? "https://hifiman.tmall.com" : "https://store.hifiman.com")
37      : /产品|ACADEMY|Flagship|Elite|Professional|Club/i.test(text) ? "/products"
38      : /技术|Technology/i.test(text) ? "/technology"
39      : /代理商|Dealer/i.test(text) ? "/dealers"
40      : /活动|Event/i.test(text) ? (cn ? "/news" : "/events")
41      : /新闻|评测|News|Review/i.test(text) ? "/news"
42      : "/about");
43  });
44  document.querySelectorAll("a.online-store").forEach((a) => route(a, cn ? "https://hifiman.tmall.com" : "https://store.hifiman.com"));
45  document.querySelectorAll(".product-mega a[data-icon]").forEach((a) => {
46    const filters = { headphone: "category=overear", iem: "category=inear", dac: "category=amplifiers", player: "category=players", goldenwave: "category=goldenwave", planar: "type=平板%20%2F%20开放式", electrostatic: "type=静电", dynamic: "category=inear", wired: "category=wireless", flagship: "level=旗舰+Flagship", elite: "level=精英+Elite", professional: "level=专业+Professional", enthusiast: "level=进阶+Academy", club: "level=畅享+Club" };
47    route(a, `/products?${filters[a.dataset.icon] || ""}`);
48  });
49  ["category=overear", "category=inear", "category=amplifiers", "category=players", "category=goldenwave"].forEach((query, index) => {
50    const card = document.querySelectorAll(".cat-card")[index]; if (card) route(card, `/products?${query}`);
51  });
52  document.querySelectorAll(".diamond-level-card").forEach((card) => {
53    const level = ({ FLAGSHIP: "旗舰+Flagship", ELITE: "精英+Elite", PROFESSIONAL: "专业+Professional", ACADEMY: "进阶+Academy", CLUB: "畅享+Club" })[card.querySelector("strong")?.textContent.trim()];
54    if (level) route(card, `/products?level=${level}`);
55  });
56  document.querySelectorAll(".product-mega .zh,.diamond-level-row .level-cn").forEach((node) => {
57    node.textContent = ({ 顶级: "精英", 次顶: "专业", 发烧: "进阶", 中端: "进阶", 入门: "畅享" })[node.textContent.trim()] || node.textContent;
58  });
59  document.querySelectorAll(".product-mega a,.diamond-level-row strong").forEach((node) => {
60    if (node.textContent.includes("Enthusiast")) node.innerHTML = node.innerHTML.replace("Enthusiast", "Academy");
61  });
62  document.querySelectorAll(".social-art,.mobile-social-art").forEach((node) => { node.hidden = true; });
63  document.querySelectorAll("main img, #mobile-home img").forEach((image) => {
64    if (image.closest(".hero,.mobile-commerce-hero,.mobile-home-header")) return;
65    image.loading = "lazy";
66    image.decoding = "async";
67  });
68  document.querySelectorAll("button").forEach((control, index) => {
69    if (!control.getAttribute("aria-label") && !control.textContent.trim())
70      control.setAttribute("aria-label", `${cn ? "轮播控制" : "Carousel control"} ${index + 1}`);
71  });
72  window.initHomePopularCarousel = popular => {
73    const popularView = popular?.querySelector(".popular-viewport");
74    const popularTrack = popularView?.querySelector(".popular-track");
75    let setPopularProducts = () => {};
76    if (popularTrack && popularTrack.children.length > 1) {
77    let original = [...popularTrack.children], count = original.length;
78    popularTrack.replaceChildren(...original.map(card => card.cloneNode(true)), ...original, ...original.map(card => card.cloneNode(true)));
79    const reduced = matchMedia("(prefers-reduced-motion: reduce)");
80    let index = count, width = 0, gap = 0, visible = 4, timer = 0, start = null, suppressUntil = 0, busy = false;
81    const position = animate => {
82      popularTrack.classList.toggle("no-animate", !animate);
83      popularTrack.style.transform = `translate3d(${-index * (width + gap)}px,0,0)`;
84      [...popularTrack.children].forEach((card, i) => {
85        const showing = i >= index && i < index + Math.ceil(visible);
86        card.setAttribute("aria-hidden", String(!showing)); card.inert = !showing;
87      });
88    };
89    const size = () => {
90      visible = Math.min(count, innerWidth < 761 ? 1.2 : innerWidth < 1101 ? 2 : 4);
91      gap = parseFloat(getComputedStyle(popularTrack).gap) || 0;
92      width = Math.floor((popularView.getBoundingClientRect().width - gap * (visible - 1)) / visible * 64) / 64;
93      popularTrack.style.setProperty("--item-width", `${width}px`);
94      position(false);
95    };
96    const normalize = () => {
97      if (index >= count * 2) index -= count;
98      if (index < count) index += count;
99      position(false); busy = false;
100    };
101    const step = direction => {
102      if (busy || count < 2) return;
103      busy = true; index += direction;
104      void popularTrack.offsetWidth;
105      position(!reduced.matches);
106      clearTimeout(timer);
107      timer = setTimeout(normalize, reduced.matches ? 1 : 420);
108    };
109    popular.querySelectorAll("[data-cycle]").forEach(button => button.addEventListener("click", () => step(Number(button.dataset.cycle))));
110    popularView.addEventListener("keydown", event => {
111      if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
112      event.preventDefault(); step(event.key === "ArrowLeft" ? -1 : 1);
113    });
114    popularView.addEventListener("dragstart", event => event.preventDefault());
115    popularView.addEventListener("pointerdown", event => { if (event.button === 0) start = { x:event.clientX, y:event.clientY }; });
116    popularView.addEventListener("pointerup", event => {
117      if (!start) return;
118      const dx = event.clientX - start.x, dy = event.clientY - start.y; start = null;
119      if (Math.abs(dx) > 45 && Math.abs(dx) > Math.abs(dy) * 1.2) {
120        step(dx < 0 ? 1 : -1); suppressUntil = performance.now() + 450;
121      }
122    });
123    popularView.addEventListener("pointercancel", () => { start = null; });
124    popularView.addEventListener("click", event => {
125      if (performance.now() < suppressUntil) { event.preventDefault(); event.stopPropagation(); }
126    }, true);
127    setInterval(() => { if (!reduced.matches && !document.hidden) step(1); }, 3000);
128    setPopularProducts = products => {
129      if (!products.length) return;
130      original = products.map(product => {
131        const card = document.createElement('article'), link = document.createElement('a'), image = document.createElement('img');
132        const copy = document.createElement('div'), title = document.createElement('h3');
133        card.className = 'product-card'; link.className = 'product-view'; copy.className = 'product-copy';
134        link.href = safeHref(product.url) || `/products/${encodeURIComponent(product.slug)}`;
135        image.src = safeHref(product.image_url) || '/assets/no-image.svg';
136        image.alt = title.textContent = product.name;
137        link.append(image); copy.append(title); card.append(link, copy);
138        return card;
139      });
140      clearTimeout(timer); busy = false;
141      count = original.length; index = count > 1 ? count : 0;
142      popularTrack.replaceChildren(...(count > 1 ? original.map(card => card.cloneNode(true)) : []),
143        ...original, ...(count > 1 ? original.map(card => card.cloneNode(true)) : []));
144      popular.querySelectorAll('[data-cycle]').forEach(button => { button.disabled = count < 2; });
145      size();
146    };
147    new ResizeObserver(size).observe(popularView); size();
148    }
149    return setPopularProducts;
150  };
151  const setPopularProducts = window.initHomePopularCarousel(document.querySelector('.popular'));
152  // Anchor editorial controls to the images, not the whole section's height.
153  window.initHomeEditorialCarousel = (section, gridSelector, arrowSelector) => {
154    const grid = section?.querySelector(gridSelector);
155    if (!section || !grid) return;
156    if (cn && gridSelector === '.listening-grid') section.querySelector('h2').textContent = '产品实测';
157    const arrows = section.querySelectorAll(arrowSelector);
158    const compact = matchMedia("(max-width:760px)");
159    const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
160    let moving = false, animations = [], touchStart = null, dragged = false, clickReset;
161    const slide = async direction => {
162      if (moving || grid.children.length < 2) return;
163      if (compact.matches) {
164        const step = grid.firstElementChild.getBoundingClientRect().width + (parseFloat(getComputedStyle(grid).columnGap
164) || 0);
165        grid.scrollBy({ left: direction * step, behavior: reducedMotion.matches ? "instant" : "smooth" });
166        return;
167      }
168      const original = direction > 0 ? grid.firstElementChild : grid.lastElementChild;
169      if (reducedMotion.matches) {
170        if (direction > 0) grid.append(original); else grid.prepend(original);
171        return;
172      }
173      moving = true;
174      grid.setAttribute("aria-busy", "true");
175      const step = original.getBoundingClientRect().width + (parseFloat(getComputedStyle(grid).columnGap) || 0);
176      const clone = original.cloneNode(true);
177      clone.setAttribute("aria-hidden", "true"); clone.setAttribute("inert", "");
178      clone.removeAttribute("id"); clone.tabIndex = -1;
179      clone.querySelectorAll("[id]").forEach(node => node.removeAttribute("id"));
180      if (direction > 0) grid.append(clone); else grid.prepend(clone);
181      animations = [...grid.children].map(card => card.animate([
182        { transform: `translateX(${direction > 0 ? 0 : -step}px)` },
183        { transform: `translateX(${direction > 0 ? -step : 0}px)` },
184      ], { duration: 700, easing: "cubic-bezier(.22,.61,.36,1)", fill: "forwards" }));
185      try { await Promise.all(animations.map(animation => animation.finished)); }
186      catch { /* A resize or reduced-motion change settles at the target card. */ }
187      finally {
188        if (direction > 0) grid.append(original); else grid.prepend(original);
189        clone.remove(); animations.forEach(animation => animation.cancel()); animations = [];
190        moving = false; grid.removeAttribute("aria-busy");
191      }
192    };
193    arrows.forEach(arrow => arrow.addEventListener("click", () => slide(arrow.classList.contains("previous") ? -1 : 1)));
194    grid.tabIndex = 0;
195    grid.setAttribute("aria-roledescription", cn ? "轮播" : "carousel");
196    grid.setAttribute("aria-labelledby", section.querySelector("h2").id);
197    grid.addEventListener("keydown", event => {
198      if (!["ArrowLeft", "ArrowRight"].includes(event.key)) return;
199      event.preventDefault(); slide(event.key === "ArrowRight" ? 1 : -1);
200    });
201    grid.addEventListener("dragstart", event => event.preventDefault());
202    // Native touch scrolling preserves vertical page scroll and scroll-snap momentum.
203    grid.addEventListener("pointerdown", event => {
204      clearTimeout(clickReset); dragged = false;
205      touchStart = event.button === 0 && event.isPrimary !== false
206        ? { x: event.clientX, y: event.clientY, pointerType: event.pointerType } : null;
207    }, { passive: true });
208    grid.addEventListener("pointermove", event => {
209      if (!touchStart) return;
210      const dx = Math.abs(event.clientX - touchStart.x), dy = Math.abs(event.clientY - touchStart.y);
211      if (dx > 10 && dx > dy) dragged = true;
212    }, { passive: true });
213    const endTouch = event => {
214      if (touchStart && event.type !== "pointercancel") {
215        const dx = event.clientX - touchStart.x, dy = event.clientY - touchStart.y;
216        if (Math.abs(dx) > 45 && Math.abs(dx) > Math.abs(dy) * 1.2 && (!compact.matches || touchStart.pointerType === "mouse")) {
217          dragged = true; slide(dx < 0 ? 1 : -1);
218        }
219      }
220      touchStart = null; clickReset = setTimeout(() => { dragged = false; }, 500);
221    };
222    grid.addEventListener("pointerup", endTouch, { passive: true });
223    grid.addEventListener("pointercancel", endTouch, { passive: true });
224    grid.addEventListener("click", event => {
225      if (dragged || moving) { event.preventDefault(); event.stopPropagation(); }
226    }, true);
227    const settle = () => {
228      animations.forEach(animation => animation.finish());
229      if (!compact.matches) grid.scrollLeft = 0;
230    };
231    compact.addEventListener("change", settle);
232    reducedMotion.addEventListener("change", settle);
233    window.addEventListener("resize", settle);
234
235    arrows.forEach(arrow => {
236      const previous = arrow.classList.contains("previous");
237      arrow.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.3" aria-hidden="true"><path d="${previous ? 'm15 5-7 7 7 7' : 'm9 5 7 7-7 7'}"/></svg>`;
238    });
239    const align = () => {
240      const photo = grid.querySelector("img");
241      if (!photo) return;
242      const bounds = section.getBoundingClientRect();
243      const row = grid.getBoundingClientRect(), image = photo.getBoundingClientRect();
244      if (!row.width || !image.height) return;
245      arrows.forEach(arrow => {
246        arrow.style.top = `${image.top - bounds.top + image.height / 2}px`;
247        const previous = arrow.classList.contains("previous");
248        arrow.style.left = previous ? `${Math.max(4, row.left - bounds.left - 48)}px` : "auto";
249        arrow.style.right = previous ? "auto" : `${Math.max(4, bounds.right - row.right - 48)}px`;
250      });
251    };
252    const observer = new ResizeObserver(align);
253    observer.observe(section); observer.observe(grid);
254    grid.querySelectorAll("img").forEach(image => { observer.observe(image); image.addEventListener("load", align); });
255    new MutationObserver(align).observe(grid, { childList: true });
256    align();
257  };
258  for (const [sectionSelector, gridSelector, arrowSelector] of [
259    [".listening-section", ".listening-grid", ".listening-arrow"],
260    [".celebrity-section", ".artist-grid", ".artist-arrow"],
261  ]) window.initHomeEditorialCarousel(document.querySelector(sectionSelector), gridSelector, arrowSelector);
262  {
263    const frames = cn ? [
264      ["/home-hero/cn-hm2000.webp", "HM2000", "", "/products/hm2000"],
265      ["/home-hero/cn-svanar-wireless-2.webp", "SVANAR Wireless 2", "", ""],
266      ["/home-hero/cn-he1000-wifi.webp", "HE1000 WiFi", "", "/products/product-348-he1000-wifi"],
267    ] : document.documentElement.lang.toLowerCase().startsWith("en") ? [
268      ["/home-hero/com-svanar-wireless-2.webp", "SVANAR Wireless 2", "", "/products/svanar-wireless-2"],
269      ["/home-hero/com-he6-remastered.webp", "HE6 Remastered", "", "/products/he6-remastered"],
270      ["/home-hero/com-he1000-wifi.webp", "HE1000 WiFi", "", "/products/he1000-wifi"],
271    ] : [
272      ["/strict/assets/product-arya-wifi.webp", "HE1000 WiFi", "WiFi Streaming Media Planar Headphone", ""],
273      ["/strict/assets/lifestyle-wireless.webp", "SVANAR Wireless 2", "A True Hi-Fi DAC Brings Music to Life", ""],
274      ["/strict/assets/components/product-he6.webp", "HE6 Remastered", "A Classic Reborn. A Legend Reheard.", ""],
275    ];
276    window.homeHeroFrames = frames;
277    const hero = document.querySelector(".hero");
278    window.homeHeroCarousel = window.createHomeCarousel(hero, frames, {
279      imageSelector: ".hero-image", titleSelector: "h1", subtitleSelector: ".hero-copy p", cn,
280      fade: document.documentElement.lang.toLowerCase().startsWith("en"),
281    });
282    document.querySelectorAll("[data-mobile-carousel]").forEach(carousel => {
283      window.createHomeCarousel(carousel, carousel.homeFrames, {
284        imageSelector: ".mobile-hero-image", titleSelector: "[data-mobile-hero-title]",
285        subtitleSelector: "[data-mobile-hero-subtitle]", cn,
286      });
287    });
288    const collections = document.querySelector(".feature-series");
289    const collectionLink = collections?.querySelector("a");
290    if (collectionLink) collectionLink.draggable = false;
291    const collectionFrames = ["HIFISTYLE", "SVANAR", "UNVEILED", "Golden Wave", "Shangri-La"].map(series => [
292      `/design/0924/series-${series.toLowerCase().replace(/ /g, "-")}-${cn ? "cn" : "en"}.webp`,
293      cn ? `${series} 系列` : `${series} collection`, "", `/products?series=${encodeURIComponent(series)}`,
294    ]);
295    window.createHomeCarousel(collections, collectionFrames, {
296      imageSelector: "img", compact: true, autoplay: true, autoplayDelay: 6000, cn,
297      onFrame: frame => {
298        route(collectionLink, frame[3]);
299        if (collectionLink) collectionLink.textContent = frame[1];
300      },
301    });
302    if (!cn) { const eventHeading = document.querySelector(".events h3"); if (eventHeading) eventHeading.textContent = "Event Calendar"; }
303  }
304  const footerCopy = document.querySelector("footer .footer-logo + p");
305  if (footerCopy) footerCopy.innerHTML = cn
306    ? 'HIFIMAN 专注高保真个人音频,产品覆盖耳机、无线音频、解码耳放、播放器与高端音源系统。<br><br>© HIFIMAN Corporation · <a href="https://beian.miit.gov.cn" rel="nofollow">津ICP备19008894号-1</a>'
307    : 'High Fidelity Personal Audio. Explore headphones, wireless audio, DAC/amplifiers, players and high-end source systems.<br><br>© HIFIMAN Corporation';
308  const supportFooter = [...document.querySelectorAll("footer .footer-links > div")].find((node) => /服务|Support/i.test(node.textContent));
309  if (supportFooter && !supportFooter.querySelector('a[href="/privacy"]')) supportFooter.insertAdjacentHTML("beforeend", '<a href="/privacy"><span class="zh">隐私政策</span><span class="en">Privacy</span></a><a href="/terms"><span class="zh">使用条款</span><span class="en">Terms</span></a>');
310
311
312  fetch(`/api/home-content?language=${encodeURIComponent(document.documentElement.lang || (cn ? "zh-CN" : "en-US"))}`, { cache: "no-store" }).then((r) => {
313    if (!r.ok) throw new Error("home content unavailable");
314    return r.json();
315  }).then(({ latest = [], reviews = [], stories = [], events = [], blocks = [], products = [], popularProducts = [] }) => {
316    window.homePopularProducts = popularProducts;
317    setPopularProducts(popularProducts);
318    // Empty CMS slots use a real SVG placeholder; it can decode successfully
319    // but must not replace the campaign carousel or disable its controls.
320    const heroes = blocks.filter((block) => {
321      if (block.block_type !== "hero" || !safeHref(block.image_url)) return false;
322      const imagePath = new URL(block.image_url, location.href).pathname;
323      return imagePath !== "/assets/no-image.svg";
324    });
325    const normalize = value => String(value || "").toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]+/g, "");
326    const productKey = value => ({ he6复刻版: "he6remastered", svanarwireless2通用版: "svanarwireless2" })[normalize(value)] || normalize(value);
327    const findProduct = title => {
328      const matches = products.filter(item => productKey(item.name) === productKey(title));
329      return matches.length === 1 ? matches[0] : null;
330    };
331    const heroFrames = heroes.length ? heroes.map(block => {
332      const fallback = !cn && window.homeHeroFrames.find(frame => frame[3] && frame[3] === block.link_url);
333      return [fallback?.[0] || block.image_url, block.title || "HIFIMAN", block.subtitle || "", block.link_url || ""];
334    }) : window.homeHeroFrames;
335    const linkedFrames = heroFrames.map(frame => {
336      const product = findProduct(frame[1]);
337      // Accept an explicit CMS detail URL only when it resolves to this site's catalog.
338      const linked = products.find(item => frame[3] === `/products/${encodeURIComponent(item.slug)}`);
339      return [frame[0], frame[1], frame[2], product || linked ? `/products/${encodeURIComponent((product || linked).slug)}` : ""];
340    });
341    window.homeHeroCarousel?.updateFrames(linkedFrames);
342    document.querySelectorAll(".popular .product-card").forEach(card => {
343      const product = findProduct(card.querySelector("h3")?.textContent);
344      const link = card.querySelector(".product-view");
345      if (product) route(link, `/products/${encodeURIComponent(product.slug)}`);
346      else if (productKey(card.querySelector("h3")?.textContent) === "he6remastered") link?.removeAttribute("href");
347    });
348    document.querySelectorAll(".mobile-product-card").forEach((card) => {
349      const label = card.querySelector("strong")?.textContent;
350      const price = card.querySelector("small");
351      const product = products.find((item) => normalize(item.name) === normalize(label));
352      if (!price || !product || product.price_display_mode !== "price" || !Number.isFinite(Number(product.price))) {
353        if (price) price.hidden = true;
354        return;
355      }
356      price.hidden = false;
357      price.textContent = new Intl.NumberFormat(cn ? "zh-CN" : "en-US", { style: "currency", currency: cn ? "CNY" : "USD" }).format(Number(product.price));
358    });
359    const eventPanel = document.querySelector(".events"), eventList = eventPanel?.querySelector(".event-list");
360    if (eventPanel && eventList) {
361      eventPanel.hidden = false;
362      eventList.replaceChildren();
363      if (!events.length) { const message = document.createElement("p"); message.className = "events-empty"; message.textContent = cn ? "暂无展会信息" : "No events available"; eventList.append(message); }
364      events.forEach((item) => {
365        const href = eventWebsite(item.source_url) || eventWebsite(item.image_url);
366        const row = document.createElement(href ? "a" : "div"), title = document.createElement("span"), date = document.createElement("small");
367        row.className = "event-item";
368        title.textContent = item.title;
369        if (href) { row.setAttribute("href", href); row.setAttribute("target", "_blank"); row.setAttribute("rel", "noopener noreferrer"); }
370        row.append(title);
371        if (item.brief) { const place = document.createElement("small"); place.textContent = item.brief; row.append(place); }
372        date.textContent = new Date(item.event_start).toLocaleDateString(cn ? "zh-CN" : "en-US", { year: "numeric", month: "2-digit", day: "2-digit", timeZone: "Asia/Shanghai" });
373        row.append(date);
374        eventList.append(row);
375      });
376    }
377    for (const [selector, items] of [[".listening-grid", reviews], [".artist-grid", stories]]) {
378      const grid = document.querySelector(selector), template = grid?.firstElementChild;
379      // Empty or unavailable content retains the gray loading cards.
380      if (!template || !items.length) continue;
381      grid.replaceChildren(...items.map(() => template.cloneNode(true)));
382    }
383    document.querySelectorAll(".listening-card").forEach((card, index) => {
384      const item = reviews[index];
385      if (!item) return;
386      const title = card.querySelector(".listening-title-line strong");
387      if (title) title.textContent = item.title;
388      const date = card.querySelector(".listening-date");
389      if (date) date.textContent = item.published_at && !Number.isNaN(Date.parse(item.published_at)) ? new Date(item.published_at).toLocaleDateString("sv-SE", { timeZone: "Asia/Shanghai" }).replaceAll("-", ".") : "";
390      const sourceName = (item.source_name || "HIFIMAN").replace(/HIFIMAN\s*旧官网/, "HIFIMAN");
391      const authorName = item.author || "HIFIMAN";
392      const source = card.querySelector(".listening-website"), author = card.querySelector(".listening-author");
393      if (source) { source.textContent = sourceName;
393 source.title = sourceName; }
394      if (author) { author.textContent = authorName; author.title = authorName; }
395      const avatar = card.querySelector(".listening-avatar");
396      if (avatar) {
397        const image = document.createElement("img");
398        image.src = item.content_type === "video" ? "/design/review-video-logo.png" : "/design/review-text-logo.png";
399        image.alt = ""; avatar.replaceChildren(image);
400      }
401      cardImage(card, item);
402      route(card, `/reviews/${encodeURIComponent(item.slug)}`);
403    });
404    document.querySelectorAll(".artist-card").forEach((card, index) => {
405      const item = stories[index];
406      if (!item) return;
407      route(card, `/stories/${encodeURIComponent(item.slug)}`);
408      const role = card.querySelector(".artist-role");
409      const name = card.querySelector(".artist-name");
410      if (role) role.textContent = item.identity || (cn ? "品牌之友" : "HIFIMAN FRIENDS");
411      if (name) name.textContent = item.author || item.title;
412      card.querySelector(".artist-title").textContent = item.title;
413      cardImage(card, item);
414    });
415    if (!cn) return;
416    document.querySelectorAll(".news-line-item").forEach((card, index) => { const item = latest[index]; if (!item) return; card.querySelector("time").textContent = new Date(item.published_at).toLocaleDateString("zh-CN", { timeZone: "Asia/Shanghai" }); const link = document.createElement("a"); link.href = `/news/${encodeURIComponent(item.slug)}`; link.textContent = item.title; card.querySelector("strong").replaceChildren(link); });
417    const review = reviews[0], reviewCard = document.querySelector(".review-feature-card");
418    if (review && reviewCard) { reviewCard.querySelector("small").textContent = `${review.source_name || "HIFIMAN"} · ${new Date(review.published_at).toLocaleDateString("zh-CN", { timeZone: "Asia/Shanghai" })}`; reviewCard.querySelector("strong").textContent = review.title; reviewCard.querySelector(".review-rating").textContent = "来源与日期可核验"; route(reviewCard.querySelector("a"), `/reviews/${encodeURIComponent(review.slug)}`); }
419    document.querySelectorAll(".event-text-item").forEach((card, index) => {
420      const item = events[index], link = card.querySelector("a");
421      if (!item) { card.hidden = true; return; }
422      card.hidden = false;
423      card.querySelector(".event-date").textContent = new Date(item.event_start).toLocaleDateString("zh-CN", { timeZone: "Asia/Shanghai" });
424      card.querySelector("h3").textContent = item.title;
425      card.querySelector("p").textContent = item.brief || "HIFIMAN 活动";
426      if (link) {
427        const href = eventWebsite(item.source_url) || eventWebsite(item.image_url);
428        link.hidden = !href;
429        link.removeAttribute("href");
430        if (href) { link.setAttribute("href", href); link.setAttribute("target", "_blank"); link.setAttribute("rel", "noopener noreferrer"); }
431      }
432    });
433  }).catch(() => {}).finally(() => {
434    window.homeContentReady = true;
435    document.dispatchEvent(new Event('home-content-ready'));
436  });
437})();

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.