(function () {
function querySelector(selector, root) {
return (root || document).querySelector(selector);
}
function querySelectorAll(selector, root) {
return (root || document).querySelectorAll(selector);
}
function normalizePageIdFromPath(pathname) {
var path = pathname || "";
var noTrailing = path.replace(/\/+$/, "");
var trimmed = noTrailing || "/";
if (trimmed === "" || trimmed === "/") {
return "index.html";
}
var segments = trimmed.split("/").filter(Boolean);
var filename = segments.length ? segments[segments.length - 1] : "";
if (!filename) {
return "index.html";
}
var lower = filename.toLowerCase();
if (lower === "index.html") {
return "index.html";
}
if (lower === "index") {
return "index.html";
}
if (lower.indexOf(".") === -1) {
return lower + ".html";
}
return lower;
}
function getCurrentPage() {
var pathname = window.location && window.location.pathname ? window.location.pathname : "";
return normalizePageIdFromPath(pathname);
}
function normalizeHrefToPageId(href) {
var trimmed = (href || "").replace(/^\s+|\s+$/g, "");
var pathname = trimmed;
var hasProtocol = trimmed.indexOf("://") !== -1;
var hasScheme = hasProtocol ? trimmed.slice(0, trimmed.indexOf("://")).toLowerCase() : "";
if (hasProtocol && hasScheme === "http") {
return "";
}
if (trimmed.indexOf("https://") === 0) {
try {
return normalizePageIdFromPath(new URL(trimmed).pathname);
} catch (error) {
return "";
}
}
pathname = trimmed.replace(/\/+$/, "").replace(/^\.\/+/, "").replace(/^\/+/, "");
if (!pathname) {
return "index.html";
}
return normalizePageIdFromPath("/" + pathname);
}
function linkNodeFromItem(item, isCurrent) {
var link = document.createElement("a");
link.href = item.href;
link.textContent = item.label;
if (isCurrent) {
link.setAttribute("aria-current", "page");
}
return link;
}
function childLinksFromItem(item) {
var children = Array.isArray(item.children) ? item.children : [];
var result = [];
var i;
for (i = 0; i < children.length; i = i + 1) {
if (children[i] && typeof children[i].href === "string" && typeof children[i].label === "string" && isSafeHref(children[i].href)) {
result.push(children[i]);
}
}
return result;
}
function isSafeHref(href) {
var trimmed = (href || "").replace(/^\s+|\s+$/g, "");
var lower = trimmed.toLowerCase();
if (!trimmed) {
return false;
}
if (lower.indexOf("javascript:") === 0 || lower.indexOf("vbscript:") === 0 || lower.indexOf("data:") === 0 || lower.indexOf("file:") === 0 || lower.indexOf("mailto:") === 0 || lower.indexOf("blob:") === 0 || lower.indexOf("ftp:") === 0 || lower.indexOf("//") === 0) {
return false;
}
if (lower.indexOf("https://") === 0) {
return true;
}
if (lower.indexOf("http://") === 0) {
return false;
}
if (lower[0] === "#") {
return false;
}
return /^[a-z0-9._/-]+\.html$/i.test(trimmed.replace(/^\.\/+/, "").replace(/^\/+/, ""));
}
function getSafeNavItems() {
var content = window.YX_CONTENT;
var result = [];
var i;
var item;
var href;
var label;
if (!content || !Array.isArray(content.nav)) {
return result;
}
for (i = 0; i < content.nav.length; i = i + 1) {
item = content.nav[i];
href = item && item.href;
label = item && item.label;
if (!item || typeof href !== "string" || typeof label !== "string") {
continue;
}
if (!isSafeHref(href)) {
continue;
}
result.push(item);
}
return result;
}
function isItemCurrent(item, currentPage) {
var pageId = normalizeHrefToPageId(item.href);
var children = childLinksFromItem(item);
var i;
if (pageId === currentPage) {
return true;
}
for (i = 0; i < children.length; i = i + 1) {
if (normalizeHrefToPageId(children[i].href) === currentPage) {
return true;
}
}
return false;
}
function renderNav() {
var navRoot = querySelector("[data-nav]");
var footerNavRoot = querySelector("[data-footer-nav]");
var navItems = getSafeNavItems();
if (!navItems.length || (!navRoot && !footerNavRoot)) {
return;
}
var currentPage = getCurrentPage();
function renderInto(root) {
if (!root) {
return;
}
while (root.firstChild) {
root.removeChild(root.firstChild);
}
var i;
for (i = 0; i < navItems.length; i = i + 1) {
var item = navItems[i];
var isCurrent = isItemCurrent(item, currentPage);
var children = childLinksFromItem(item);
if (children.length && root.classList.contains("site-nav")) {
var group = document.createElement("div");
var menu = document.createElement("div");
var label = linkNodeFromItem(item, isCurrent);
var j;
group.className = "nav-group";
menu.className = "nav-submenu";
label.className = "nav-parent";
label.setAttribute("aria-haspopup", "true");
for (j = 0; j < children.length; j = j + 1) {
menu.appendChild(linkNodeFromItem(children[j], normalizeHrefToPageId(children[j].href) === currentPage));
}
group.appendChild(label);
group.appendChild(menu);
root.appendChild(group);
} else {
root.appendChild(linkNodeFromItem(item, isCurrent));
}
}
}
renderInto(navRoot);
renderInto(footerNavRoot);
}
/**
* 资源根地址。
*
* 部署流程会把 HTML 里的资源路径改写成 CDN 绝对地址(如
* //cdn.example.com/site/assets/styles-x.css),但不会改写 JS 字符串里的
* 路径。页脚二维码的路径写在 content.js 里,若直接使用就会相对当前域名
* 解析、指向不存在的地址而 404。
*
* 这里从任一已被改写的资源地址反推资源根,让 JS 里的相对路径也落到同一
* 位置。本地开发时样式表是 "assets/css/styles.css"(前缀为 0),返回空串,
* 行为与改动前一致。
*/
function assetBase() {
var link = document.querySelector('link[href*="assets/"]');
if (!link) {
return "";
}
var href = link.getAttribute("href") || "";
var index = href.indexOf("assets/");
return index > 0 ? href.slice(0, index) : "";
}
/**
* 修正 上的资源链接(论文、白皮书等文档)。
*
* 构建与部署流程只改写 Vite 处理过的资源路径, 里的相对路径不会
* 被改写,线上会相对当前域名解析而 404。这里在运行时统一补上资源根。
* 已被改写为绝对地址的链接不匹配选择器,因此不会重复加前缀。
*/
/**
* 兜底加载 ip-chat.js(品牌 IP 咨询对话)。
*
* 线上出现过同目录下 content.js / site.js 正常、ip-chat.js 却 404 的情况
* (部署时漏传单个文件),导致 IP 点击后没有问答面板。这里在页面加载完成
* 后检测就绪标记,缺失则按资源根重新加载一次。
*/
function ensureIpChat() {
var float = document.querySelector("[data-ip-float]");
if (!float) {
return;
}
// 判断依据用 ip-chat.js 加载成功后必然产生的副作用(给形象加 role="button"),
// 而不是仅在最新版里才有的标记 —— 否则线上是旧版时会重复注入第二次。
var figure = float.querySelector(".yx-ip-figure");
if (window.YX_IP_CHAT_READY || (figure && figure.getAttribute("role") === "button")) {
return;
}
var script = document.createElement("script");
script.src = assetBase() + "assets/js/ip-chat.js";
script.async = false;
document.body.appendChild(script);
}
function fixAssetLinks() {
var base = assetBase();
if (!base) {
return;
}
var links = document.querySelectorAll('a[href^="assets/"], a[href^="./assets/"]');
var i;
for (i = 0; i < links.length; i = i + 1) {
var href = links[i].getAttribute("href") || "";
links[i].setAttribute("href", base + href.replace(/^\.\//, ""));
}
}
function renderFooterDetails() {
var root = querySelector("[data-footer-details]");
var content = window.YX_CONTENT;
if (!root || !content || !content.footer) {
return;
}
var groups = [
{ title: "社媒账号", items: content.footer.social },
{ title: "销售渠道", items: content.footer.channels },
{ title: "联系我们", items: content.footer.contact }
];
while (root.firstChild) {
root.removeChild(root.firstChild);
}
groups.forEach(function (group) {
// 该组没有条目时整组不渲染,避免出现只有标题的空列
if (!group.items || !group.items.length) {
return;
}
var section = document.createElement("section");
var title = document.createElement("h3");
var list = document.createElement("ul");
title.textContent = group.title;
section.className = "footer-detail-group";
section.appendChild(title);
(group.items || []).forEach(function (entry) {
// 兼容两种写法:纯字符串,或 { label, href }
var text = typeof entry === "string" ? entry : (entry && entry.label) || "";
var href = typeof entry === "string" ? "" : (entry && entry.href) || "";
var qr = typeof entry === "string" ? "" : (entry && entry.qr) || "";
if (!text) {
return;
}
// 公众号等没有网页地址的条目用二维码承载,悬停/聚焦时显示
if (qr) {
var qrItem = document.createElement("li");
qrItem.className = "footer-detail-qr";
var qrTrigger = document.createElement("span");
qrTrigger.className = "footer-detail-qr-label";
qrTrigger.textContent = text;
qrTrigger.setAttribute("tabindex", "0");
var qrImg = document.createElement("img");
qrImg.src = assetBase() + qr;
qrImg.alt = text + " 二维码";
qrImg.loading = "lazy";
qrItem.appendChild(qrTrigger);
qrItem.appendChild(qrImg);
list.appendChild(qrItem);
return;
}
// 页脚只允许站外 https 与电话拨号,其余一律降级为纯文字
var lowerHref = href.toLowerCase();
var isExternal = lowerHref.indexOf("https://") === 0;
var isTel = /^tel:\+?[0-9-]{3,20}$/.test(lowerHref);
if (href && !isExternal && !isTel) {
href = "";
}
var item = document.createElement("li");
if (href) {
var link = document.createElement("a");
link.className = "footer-detail-link";
link.href = href;
link.textContent = text;
// 站外地址在新标签打开,避免用户离开官网
if (isExternal) {
link.target = "_blank";
link.rel = "noreferrer";
}
item.appendChild(link);
} else {
item.textContent = text;
}
list.appendChild(item);
});
section.appendChild(list);
root.appendChild(section);
});
}
function setupMenuToggle() {
var toggle = querySelector("[data-menu-toggle]");
var navRoot = querySelector("[data-nav]");
if (!toggle || !navRoot) {
return;
}
if (!navRoot.id) {
navRoot.id = "site-nav";
}
toggle.setAttribute("aria-controls", navRoot.id);
if (!toggle.hasAttribute("aria-expanded")) {
toggle.setAttribute("aria-expanded", "false");
}
toggle.addEventListener("click", function () {
var expanded = toggle.getAttribute("aria-expanded") === "true";
var nextExpanded = !expanded;
toggle.setAttribute("aria-expanded", String(nextExpanded));
navRoot.classList.toggle("is-open", nextExpanded);
});
}
function applyReducedMotion() {
if (!window.matchMedia) {
return;
}
var query = window.matchMedia("(prefers-reduced-motion: reduce)");
if (query.matches) {
document.documentElement.classList.add("reduce-motion");
}
}
function fillYear() {
var yearNodes = querySelectorAll("[data-year]");
var year = String(new Date().getFullYear());
var i;
for (i = 0; i < yearNodes.length; i = i + 1) {
yearNodes[i].textContent = year;
}
}
function setupScrollReveal() {
var targets = querySelectorAll(".section, .statement-item, .heritage-item, .product-feature, .news-row, .wide-image");
var i;
if (!("IntersectionObserver" in window)) {
for (i = 0; i < targets.length; i = i + 1) {
targets[i].classList.add("reveal");
}
return;
}
var observer = new IntersectionObserver(function (entries) {
var j;
for (j = 0; j < entries.length; j = j + 1) {
if (entries[j].isIntersecting) {
entries[j].target.classList.add("reveal");
observer.unobserve(entries[j].target);
}
}
}, { rootMargin: "0px 0px -12% 0px", threshold: 0.12 });
for (i = 0; i < targets.length; i = i + 1) {
observer.observe(targets[i]);
}
}
function setupCertLightbox() {
var triggers = querySelectorAll("[data-lightbox]");
if (!triggers.length) {
return;
}
var overlay = document.createElement("div");
overlay.className = "lightbox";
overlay.setAttribute("hidden", "");
overlay.setAttribute("role", "dialog");
overlay.setAttribute("aria-modal", "true");
overlay.setAttribute("aria-label", "证书放大查看");
var figure = document.createElement("figure");
figure.className = "lightbox-figure";
var img = document.createElement("img");
img.alt = "";
var caption = document.createElement("figcaption");
caption.className = "lightbox-figcaption";
var closeBtn = document.createElement("button");
closeBtn.type = "button";
closeBtn.className = "lightbox-close";
closeBtn.setAttribute("aria-label", "关闭");
closeBtn.textContent = "×";
figure.appendChild(img);
figure.appendChild(caption);
overlay.appendChild(figure);
overlay.appendChild(closeBtn);
document.body.appendChild(overlay);
function open(trigger) {
// data-lightbox 是自定义属性,Vite 构建不会重写其路径;
// 触发器内部
的 src 已被构建正确重写(含 CDN 地址),优先取用,避免线上放大图 404
var thumb = trigger.querySelector("img");
img.src = (thumb && thumb.src) || trigger.getAttribute("data-lightbox") || "";
img.alt = trigger.getAttribute("data-lightbox-title") || "";
caption.textContent = trigger.getAttribute("data-lightbox-title") || "";
overlay.hidden = false;
document.body.style.overflow = "hidden";
}
function close() {
overlay.hidden = true;
document.body.style.overflow = "";
}
var i;
for (i = 0; i < triggers.length; i = i + 1) {
triggers[i].addEventListener("click", function (event) {
open(event.currentTarget);
});
}
overlay.addEventListener("click", function (event) {
if (event.target === overlay || event.target === closeBtn) {
close();
}
});
document.addEventListener("keydown", function (event) {
if (event.key === "Escape" && !overlay.hidden) {
close();
}
});
}
/**
* 品牌 IP 悬浮(天天 / 成成)
*
* 随页面滚动切换形象:滚到科学 / 产品相关区块时换成戴眼镜的严博士,
* 滚到品类、品牌主张、动态等日常区块时换成温暖治愈形象。
* 形象与文案由页面上的 [data-ip-float] 容器通过 data 属性声明。
*/
function setupIpFloat() {
var root = querySelector("[data-ip-float]");
if (!root) {
return;
}
var stage = querySelector(".yx-ip-stage", root);
var figure = querySelector(".yx-ip-figure", root);
var bubble = querySelector(".yx-ip-bubble", root);
var closeBtn = querySelector(".yx-ip-close", root);
var images = querySelectorAll("img[data-ip-mood]", root);
if (!figure || !images.length) {
return;
}
// 声明各区块对应的形象:data-ip-mood="science" / "daily"
var zones = [];
var zoneNodes = querySelectorAll("[data-ip-zone]");
var i;
for (i = 0; i < zoneNodes.length; i = i + 1) {
zones.push({
el: zoneNodes[i],
mood: zoneNodes[i].getAttribute("data-ip-zone") || "daily",
text: zoneNodes[i].getAttribute("data-ip-text") || ""
});
}
var dismissed = false;
var currentMood = "";
var bubbleTimer = null;
function setMood(mood, text) {
if (mood === currentMood) {
return;
}
currentMood = mood;
var j;
var matched = false;
for (j = 0; j < images.length; j = j + 1) {
var isTarget = images[j].getAttribute("data-ip-mood") === mood;
if (isTarget) {
matched = true;
}
images[j].classList.toggle("is-active", isTarget);
}
// 没有对应形象时回落到第一张,避免整块空白
if (!matched) {
images[0].classList.add("is-active");
}
if (!bubble) {
return;
}
if (text) {
bubble.textContent = text;
bubble.classList.add("is-shown");
if (bubbleTimer) {
window.clearTimeout(bubbleTimer);
}
bubbleTimer = window.setTimeout(function () {
bubble.classList.remove("is-shown");
}, 4200);
} else {
bubble.classList.remove("is-shown");
}
}
function currentZone() {
// 取视口中线所处的区块,滚动到哪个区块就用哪个形象
var mid = window.innerHeight / 2;
var found = null;
var j;
for (j = 0; j < zones.length; j = j + 1) {
var rect = zones[j].el.getBoundingClientRect();
if (rect.top <= mid && rect.bottom >= mid) {
found = zones[j];
}
}
return found;
}
function update() {
if (dismissed) {
return;
}
// 首屏不打扰,往下滚一点再出现
var scrolled = window.pageYOffset || document.documentElement.scrollTop || 0;
var show = scrolled > window.innerHeight * 0.45;
root.classList.toggle("is-visible", show);
var zone = currentZone();
if (zone) {
setMood(zone.mood, zone.text);
}
}
var ticking = false;
function onScroll() {
if (ticking) {
return;
}
ticking = true;
window.requestAnimationFrame(function () {
update();
ticking = false;
});
}
if (closeBtn) {
closeBtn.addEventListener("click", function () {
dismissed = true;
root.classList.remove("is-visible");
});
}
if (stage && bubble) {
stage.addEventListener("mouseenter", function () {
var zone = currentZone();
if (zone && zone.text) {
bubble.textContent = zone.text;
bubble.classList.add("is-shown");
}
});
stage.addEventListener("mouseleave", function () {
bubble.classList.remove("is-shown");
});
}
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", onScroll);
update();
}
/**
* 品牌故事页板块切换
*
* 顶部标签控制 [data-story-panel] 的显示,URL hash 可直达某个板块,
* 浏览器前进/后退也能正确切换。
*/
function setupStoryTabs() {
var tabsRoot = querySelector("[data-story-tabs]");
if (!tabsRoot) {
return;
}
var tabs = querySelectorAll("[data-story-tab]", tabsRoot);
var panels = querySelectorAll("[data-story-panel]");
if (!tabs.length || !panels.length) {
return;
}
function activate(name, updateHash) {
var matched = false;
var i;
for (i = 0; i < panels.length; i = i + 1) {
var isTarget = panels[i].getAttribute("data-story-panel") === name;
panels[i].hidden = !isTarget;
if (isTarget) {
matched = true;
}
}
// hash 指向不存在的板块时回落到第一个,避免整页空白
if (!matched) {
panels[0].hidden = false;
name = panels[0].getAttribute("data-story-panel");
}
for (i = 0; i < tabs.length; i = i + 1) {
var on = tabs[i].getAttribute("data-story-tab") === name;
tabs[i].classList.toggle("is-active", on);
tabs[i].setAttribute("aria-selected", on ? "true" : "false");
tabs[i].setAttribute("tabindex", on ? "0" : "-1");
}
if (updateHash && window.history && window.history.replaceState) {
window.history.replaceState(null, "", "#" + name);
}
}
var i;
for (i = 0; i < tabs.length; i = i + 1) {
tabs[i].addEventListener("click", function (event) {
event.preventDefault();
activate(event.currentTarget.getAttribute("data-story-tab"), true);
tabsRoot.scrollIntoView({ block: "start" });
});
}
// 左右方向键在标签间移动,符合 tablist 无障碍惯例
tabsRoot.addEventListener("keydown", function (event) {
if (event.key !== "ArrowRight" && event.key !== "ArrowLeft") {
return;
}
var list = [];
var j;
for (j = 0; j < tabs.length; j = j + 1) {
list.push(tabs[j]);
}
var current = list.indexOf(document.activeElement);
if (current === -1) {
return;
}
event.preventDefault();
var step = event.key === "ArrowRight" ? 1 : -1;
var next = list[(current + step + list.length) % list.length];
next.focus();
activate(next.getAttribute("data-story-tab"), true);
});
window.addEventListener("hashchange", function () {
activate((window.location.hash || "").replace("#", ""), false);
});
activate((window.location.hash || "").replace("#", ""), false);
}
function init() {
renderNav();
renderFooterDetails();
fixAssetLinks();
// ip-chat.js 在 site.js 之后引入,故延到 load 再判断是否已就绪
if (document.readyState === "complete") {
ensureIpChat();
} else {
window.addEventListener("load", ensureIpChat);
}
setupMenuToggle();
applyReducedMotion();
fillYear();
setupScrollReveal();
setupCertLightbox();
setupIpFloat();
setupStoryTabs();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();