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.