| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625 |
- /**
- * 亿图腾 · 浠艾福 — 企业官网交互脚本
- * Mobile nav, scroll animations, header effects
- */
- document.addEventListener('DOMContentLoaded', () => {
- // --------------------------------------------------------
- // 1. Mobile Nav Toggle
- // --------------------------------------------------------
- const navToggle = document.getElementById('navToggle');
- const nav = document.getElementById('nav');
- if (navToggle && nav) {
- navToggle.addEventListener('click', () => {
- navToggle.classList.toggle('active');
- nav.classList.toggle('open');
- const isOpen = nav.classList.contains('open');
- navToggle.setAttribute('aria-label', isOpen ? '关闭导航' : '切换导航');
- });
- // Close nav on link click
- nav.querySelectorAll('.nav-link').forEach(link => {
- link.addEventListener('click', () => {
- navToggle.classList.remove('active');
- nav.classList.remove('open');
- navToggle.setAttribute('aria-label', '切换导航');
- });
- });
- }
- // --------------------------------------------------------
- // 2. Header shadow on scroll
- // --------------------------------------------------------
- const header = document.getElementById('header');
- if (header) {
- let ticking = false;
- window.addEventListener('scroll', () => {
- if (!ticking) {
- requestAnimationFrame(() => {
- header.classList.toggle('scrolled', window.scrollY > 40);
- ticking = false;
- });
- ticking = true;
- }
- }, { passive: true });
- }
- // --------------------------------------------------------
- // 3. Active nav link tracking (Intersection Observer)
- // --------------------------------------------------------
- const sections = document.querySelectorAll('section[id]');
- const navLinks = document.querySelectorAll('.nav-link');
- if (sections.length && navLinks.length) {
- const observerOptions = {
- rootMargin: '-50% 0px -50% 0px',
- threshold: 0
- };
- const sectionObserver = new IntersectionObserver((entries) => {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- const id = entry.target.getAttribute('id');
- navLinks.forEach(link => {
- link.classList.remove('active');
- if (link.getAttribute('href') === `#${id}`) {
- link.classList.add('active');
- }
- });
- }
- });
- }, observerOptions);
- sections.forEach(section => sectionObserver.observe(section));
- }
- // --------------------------------------------------------
- // 4. Scroll-triggered reveal animations
- // --------------------------------------------------------
- const revealElements = document.querySelectorAll(
- '.about-card, .dimension-card, .feature-card, .role-card, ' +
- '.stat, .contact-item, .cta-card, .product-quote, .pillar-card'
- );
- if (revealElements.length) {
- // Add reveal class
- revealElements.forEach(el => el.classList.add('reveal'));
- const revealObserver = new IntersectionObserver((entries) => {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- // Stagger animation based on index
- const siblings = Array.from(entry.target.parentElement.children)
- .filter(child => child.classList.contains('reveal'));
- const index = siblings.indexOf(entry.target);
- entry.target.style.transitionDelay = `${index * 80}ms`;
- entry.target.classList.add('visible');
- revealObserver.unobserve(entry.target);
- }
- });
- }, {
- threshold: 0.15,
- rootMargin: '0px 0px -40px 0px'
- });
- revealElements.forEach(el => revealObserver.observe(el));
- }
- // --------------------------------------------------------
- // 5. Smooth scroll for anchor links (progressive enhancement)
- // --------------------------------------------------------
- document.querySelectorAll('a[href^="#"]').forEach(anchor => {
- anchor.addEventListener('click', (e) => {
- const targetId = anchor.getAttribute('href');
- if (targetId === '#') return;
- const target = document.querySelector(targetId);
- if (target) {
- e.preventDefault();
- const headerOffset = 64; // --header-height
- const targetPosition = target.getBoundingClientRect().top + window.scrollY - headerOffset;
- window.scrollTo({
- top: targetPosition,
- behavior: 'smooth'
- });
- }
- });
- });
- // --------------------------------------------------------
- // 6. Orbit animation pause on hover (accessibility)
- // --------------------------------------------------------
- const orbitItems = document.querySelectorAll('.orbit-item');
- orbitItems.forEach(item => {
- item.addEventListener('mouseenter', () => {
- item.style.animationPlayState = 'paused';
- });
- item.addEventListener('mouseleave', () => {
- item.style.animationPlayState = 'running';
- });
- });
- // --------------------------------------------------------
- // 7. Performance: debounced resize handler
- // --------------------------------------------------------
- let resizeTimer;
- window.addEventListener('resize', () => {
- clearTimeout(resizeTimer);
- resizeTimer = setTimeout(() => {
- // Close mobile nav on resize to desktop
- if (window.innerWidth > 768 && nav && nav.classList.contains('open')) {
- navToggle.classList.remove('active');
- nav.classList.remove('open');
- navToggle.setAttribute('aria-label', '切换导航');
- }
- // Redraw wuxing canvas on resize
- drawWuxingDiagram();
- }, 200);
- }, { passive: true });
- // --------------------------------------------------------
- // 8. Wuxing Diagram — Canvas 2D (redesigned elegant version)
- // --------------------------------------------------------
- function drawWuxingDiagram() {
- const canvas = document.getElementById('wuxingCanvas');
- if (!canvas) return;
- const container = canvas.parentElement;
- const size = container.clientWidth;
- if (size < 10) return;
- const dpr = window.devicePixelRatio || 1;
- canvas.width = size * dpr;
- canvas.height = size * dpr;
- canvas.style.width = size + 'px';
- canvas.style.height = size + 'px';
- const ctx = canvas.getContext('2d');
- ctx.scale(dpr, dpr);
- const cx = size / 2;
- const cy = size / 2;
- const outerR = size * 0.38;
- // ---- calculate 5 outer vertices ----
- function calcPentagonVertices(cx, cy, radius) {
- const pts = [];
- for (let i = 0; i < 5; i++) {
- const angle = -Math.PI / 2 + i * 2 * Math.PI / 5;
- pts.push({
- x: cx + radius * Math.cos(angle),
- y: cy + radius * Math.sin(angle)
- });
- }
- return pts;
- }
- const outer = calcPentagonVertices(cx, cy, outerR);
- // dimension data [身, 智, 富, 行, 心] (top clockwise)
- const dims = [
- { name: '身', element: '土', color: '#5D4037', bg: '#F5EFEB', desc: '主动健康' },
- { name: '智', element: '金', color: '#C0A060', bg: '#F9F4EC', desc: '赋能未来' },
- { name: '富', element: '水', color: '#0EA5E9', bg: '#ECFEFF', desc: '利他创富' },
- { name: '行', element: '木', color: '#10B981', bg: '#ECFDF5', desc: '人际和谐' },
- { name: '心', element: '火', color: '#EF4444', bg: '#FEF2F2', desc: '子女传承' }
- ];
- // ---- helpers ----
- function midPoint(p1, p2) {
- return { x: (p1.x + p2.x) / 2, y: (p1.y + p2.y) / 2 };
- }
- // ================================================================
- // 1. Background — soft radial glow
- // ================================================================
- var bgGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, outerR * 1.4);
- bgGrad.addColorStop(0, 'rgba(74, 155, 215, 0.04)');
- bgGrad.addColorStop(0.6, 'rgba(74, 155, 215, 0.02)');
- bgGrad.addColorStop(1, 'rgba(74, 155, 215, 0)');
- ctx.fillStyle = bgGrad;
- ctx.beginPath();
- ctx.arc(cx, cy, outerR * 1.4, 0, Math.PI * 2);
- ctx.fill();
- // ================================================================
- // 2. Outer orbit ring
- // ================================================================
- ctx.beginPath();
- ctx.arc(cx, cy, outerR + outerR * 0.08, 0, Math.PI * 2);
- ctx.strokeStyle = 'rgba(180, 170, 160, 0.25)';
- ctx.lineWidth = 1;
- ctx.setLineDash([3, 5]);
- ctx.stroke();
- ctx.setLineDash([]);
- // Inner orbit ring
- ctx.beginPath();
- ctx.arc(cx, cy, outerR * 0.55, 0, Math.PI * 2);
- ctx.strokeStyle = 'rgba(180, 170, 160, 0.12)';
- ctx.lineWidth = 1;
- ctx.setLineDash([2, 4]);
- ctx.stroke();
- ctx.setLineDash([]);
- // ================================================================
- // 3. 相克 (pentagram star) — drawn first so it sits behind
- // ================================================================
- var keOrder = [0, 2, 4, 1, 3]; // 身→富→心→智→行→身
- var keColor = 'rgba(74, 155, 215, 0.45)';
- var nodeR = Math.max(24, size * 0.068);
- for (var i = 0; i < 5; i++) {
- var from = outer[keOrder[i % 5]];
- var to = outer[keOrder[(i + 1) % 5]];
- // dashed line
- ctx.beginPath();
- ctx.moveTo(from.x, from.y);
- ctx.lineTo(to.x, to.y);
- ctx.strokeStyle = keColor;
- ctx.lineWidth = 1.5;
- ctx.setLineDash([4, 5]);
- ctx.stroke();
- ctx.setLineDash([]);
- // Arrow near the end (before the target node)
- var dx = to.x - from.x;
- var dy = to.y - from.y;
- var len = Math.sqrt(dx * dx + dy * dy) || 1;
- var arrowDist = nodeR + 8;
- var ax = to.x - (dx / len) * arrowDist;
- var ay = to.y - (dy / len) * arrowDist;
- var angle = Math.atan2(dy, dx);
- var s = 8;
- ctx.save();
- ctx.translate(ax, ay);
- ctx.rotate(angle);
- ctx.beginPath();
- ctx.moveTo(s, 0);
- ctx.lineTo(-s * 0.5, -s * 0.6);
- ctx.lineTo(-s * 0.5, s * 0.6);
- ctx.closePath();
- ctx.fillStyle = 'rgba(74, 155, 215, 0.6)';
- ctx.fill();
- ctx.restore();
- }
- // ================================================================
- // 4. 相生 (outer pentagon) — elegant gradient lines
- // ================================================================
- var shengColors = ['#4ECDC4', '#45B7AA', '#4ECDC4', '#45B7AA', '#4ECDC4'];
- for (var i = 0; i < 5; i++) {
- var from = outer[i];
- var to = outer[(i + 1) % 5];
- // Glow shadow under the line
- ctx.beginPath();
- ctx.moveTo(from.x, from.y);
- ctx.lineTo(to.x, to.y);
- ctx.strokeStyle = 'rgba(78, 205, 196, 0.15)';
- ctx.lineWidth = 6;
- ctx.shadowColor = 'rgba(78, 205, 196, 0.3)';
- ctx.shadowBlur = 8;
- ctx.stroke();
- ctx.shadowColor = 'transparent';
- ctx.shadowBlur = 0;
- // Main line with gradient
- var grad = ctx.createLinearGradient(from.x, from.y, to.x, to.y);
- grad.addColorStop(0, shengColors[i]);
- grad.addColorStop(1, shengColors[(i + 1) % 5]);
- ctx.beginPath();
- ctx.moveTo(from.x, from.y);
- ctx.lineTo(to.x, to.y);
- ctx.strokeStyle = grad;
- ctx.lineWidth = 2.5;
- ctx.lineCap = 'round';
- ctx.stroke();
- // Elegant arrow at midpoint
- var mp = midPoint(from, to);
- var angle = Math.atan2(to.y - from.y, to.x - from.x);
- var s = 11;
- ctx.save();
- ctx.translate(mp.x, mp.y);
- ctx.rotate(angle);
- ctx.beginPath();
- ctx.moveTo(s, 0);
- ctx.lineTo(-s * 0.4, -s * 0.5);
- ctx.lineTo(-s * 0.2, 0);
- ctx.lineTo(-s * 0.4, s * 0.5);
- ctx.closePath();
- ctx.fillStyle = 'rgba(78, 205, 196, 0.8)';
- ctx.fill();
- ctx.restore();
- }
- // ================================================================
- // 5. Dimension nodes — multi-layered polished circles
- // ================================================================
- for (var i = 0; i < 5; i++) {
- var v = outer[i];
- var d = dims[i];
- // Outer soft glow
- var glowGrad = ctx.createRadialGradient(v.x, v.y, nodeR * 0.3, v.x, v.y, nodeR * 2.2);
- glowGrad.addColorStop(0, d.color + '22');
- glowGrad.addColorStop(1, d.color + '00');
- ctx.fillStyle = glowGrad;
- ctx.beginPath();
- ctx.arc(v.x, v.y, nodeR * 2.2, 0, Math.PI * 2);
- ctx.fill();
- // Outer ring (stroke only)
- ctx.beginPath();
- ctx.arc(v.x, v.y, nodeR + 4, 0, Math.PI * 2);
- ctx.strokeStyle = d.color + '55';
- ctx.lineWidth = 1.5;
- ctx.stroke();
- // Inner white fill
- ctx.beginPath();
- ctx.arc(v.x, v.y, nodeR, 0, Math.PI * 2);
- ctx.fillStyle = d.bg;
- ctx.fill();
- // Colored border
- ctx.beginPath();
- ctx.arc(v.x, v.y, nodeR, 0, Math.PI * 2);
- ctx.strokeStyle = d.color;
- ctx.lineWidth = 2.5;
- ctx.stroke();
- // Soft inner highlight (top-left)
- var hlGrad = ctx.createRadialGradient(
- v.x - nodeR * 0.3, v.y - nodeR * 0.3, 0,
- v.x, v.y, nodeR
- );
- hlGrad.addColorStop(0, 'rgba(255,255,255,0.6)');
- hlGrad.addColorStop(0.5, 'rgba(255,255,255,0)');
- hlGrad.addColorStop(1, 'rgba(0,0,0,0.03)');
- ctx.beginPath();
- ctx.arc(v.x, v.y, nodeR, 0, Math.PI * 2);
- ctx.fillStyle = hlGrad;
- ctx.fill();
- // Dimension name
- var fs = Math.max(15, nodeR * 0.58);
- ctx.font = 'bold ' + fs + 'px "PingFang SC","Microsoft YaHei",sans-serif';
- ctx.textAlign = 'center';
- ctx.textBaseline = 'middle';
- ctx.fillStyle = d.color;
- ctx.fillText(d.name, v.x, v.y - fs * 0.25);
- // Element badge (small pill below name)
- var elemSize = Math.max(10, fs * 0.5);
- ctx.font = elemSize + 'px "PingFang SC","Microsoft YaHei",sans-serif';
- ctx.fillStyle = d.color;
- ctx.globalAlpha = 0.55;
- ctx.fillText(d.element, v.x, v.y + fs * 0.55);
- ctx.globalAlpha = 1;
- // Small decorative dot on outer ring (node edge highlight)
- for (var dAngle = 0; dAngle < 5; dAngle++) {
- var da = -Math.PI / 2 + dAngle * 2 * Math.PI / 5;
- var dx = v.x + (nodeR + 2) * Math.cos(da);
- var dy = v.y + (nodeR + 2) * Math.sin(da);
- ctx.beginPath();
- ctx.arc(dx, dy, 1.5, 0, Math.PI * 2);
- ctx.fillStyle = d.color + '66';
- ctx.fill();
- }
- }
- // ================================================================
- // 6. Center — Tai Chi overlay (handled by HTML/CSS SVG)
- // ================================================================
- // (The center emblem is replaced by the .taiji-center SVG overlay.)
- }
- // --------------------------------------------------------
- // 8. Knowledge Base — fetch featured articles from API
- // --------------------------------------------------------
- const API_BASE = 'https://cfc.etotem.com.cn';
- (function loadFeaturedArticles() {
- const grid = document.getElementById('knowledgeGrid');
- const loading = document.getElementById('knowledgeLoading');
- if (!grid) return;
- fetch(API_BASE + '/api/articles/featured', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ size: 4 })
- })
- .then(function (res) { return res.json(); })
- .then(function (result) {
- if (loading) loading.style.display = 'none';
- if (!result.data || !result.data.length) {
- grid.innerHTML = '<p class="knowledge-error">暂无内容,敬请期待</p>';
- return;
- }
- var html = '';
- var dimColors = {
- '身': '#FF8C42', '智': '#6366F1', '富': '#F59E0B',
- '行': '#10B981', '心': '#FF6B9D'
- };
- result.data.forEach(function (article) {
- var relatedDim = article.relatedDimensions || '';
- var dimTag = '';
- if (relatedDim && dimColors[relatedDim]) {
- dimTag = '<span class="dimension-tag" style="background:' + dimColors[relatedDim] + '">' + relatedDim + '</span>';
- } else if (relatedDim) {
- dimTag = '<span class="dimension-tag" style="background:#4A9BD7">' + relatedDim + '</span>';
- }
- var coverHtml = article.coverImage
- ? '<img class="knowledge-card-cover" src="' + API_BASE + article.coverImage + '" alt="' + article.title + '" loading="lazy">'
- : '<div class="knowledge-card-cover"><svg viewBox="0 0 24 24" width="40" height="40" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M4 19.5A2.5 2.5 0 016.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/><path d="M8 7h6M8 11h8M8 15h4"/></svg></div>';
- var catName = article.categoryName || '';
- html += '<div class="knowledge-card">'
- + coverHtml
- + '<div class="knowledge-card-body">'
- + (catName ? '<span class="knowledge-card-category">' + catName + '</span>' : '')
- + '<h3 class="knowledge-card-title">' + article.title + '</h3>'
- + (article.summary ? '<p class="knowledge-card-summary">' + article.summary + '</p>' : '')
- + '<div class="knowledge-card-meta">'
- + dimTag
- + (article.readTime ? '<span>阅读约 ' + article.readTime + ' 分钟</span>' : '')
- + '</div></div></div>';
- });
- grid.innerHTML = html;
- })
- .catch(function () {
- if (loading) loading.style.display = 'none';
- grid.innerHTML = '<p class="knowledge-error">暂时无法加载,请稍后再试</p>';
- });
- })();
- // --------------------------------------------------------
- // 9. Floating CTA toggle
- // --------------------------------------------------------
- const floatCtaBtn = document.getElementById('floatCtaBtn');
- const floatCtaPopup = document.getElementById('floatCtaPopup');
- const floatCtaClose = document.getElementById('floatCtaClose');
- if (floatCtaBtn && floatCtaPopup) {
- floatCtaBtn.addEventListener('click', (e) => {
- e.stopPropagation();
- floatCtaPopup.classList.toggle('open');
- });
- if (floatCtaClose) {
- floatCtaClose.addEventListener('click', (e) => {
- e.stopPropagation();
- floatCtaPopup.classList.remove('open');
- });
- }
- document.addEventListener('click', () => {
- floatCtaPopup.classList.remove('open');
- });
- floatCtaPopup.addEventListener('click', (e) => {
- e.stopPropagation();
- });
- }
- // --------------------------------------------------------
- // 9. Cookie consent banner
- // --------------------------------------------------------
- const cookieConsent = document.getElementById('cookieConsent');
- const cookieConsentBtn = document.getElementById('cookieConsentBtn');
- if (cookieConsent && cookieConsentBtn) {
- if (!localStorage.getItem('cookieConsent')) {
- setTimeout(() => cookieConsent.classList.add('visible'), 600);
- }
- cookieConsentBtn.addEventListener('click', () => {
- cookieConsent.classList.remove('visible');
- localStorage.setItem('cookieConsent', 'true');
- });
- }
- // Initial draw
- drawWuxingDiagram();
- // Redraw on orientation/resize via ResizeObserver
- if (window.ResizeObserver) {
- const wuxingContainer = document.querySelector('.wuxing-diagram');
- if (wuxingContainer) {
- const ro = new ResizeObserver(() => drawWuxingDiagram());
- ro.observe(wuxingContainer);
- }
- }
-
- });
- // --------------------------------------------------------
- // 9. User behavior analytics — Clarity custom events
- // --------------------------------------------------------
- (function() {
- var SCROLL_DEPTHS = [25, 50, 75, 90, 100];
- var reportedDepths = {};
- function trackScrollDepth() {
- var docHeight = document.documentElement.scrollHeight - window.innerHeight;
- if (docHeight <= 0) return;
- var scrolled = Math.round((window.scrollY / docHeight) * 100);
- SCROLL_DEPTHS.forEach(function(depth) {
- if (scrolled >= depth && !reportedDepths[depth]) {
- reportedDepths[depth] = true;
- if (window.clarity) {
- window.clarity('event', 'scroll_depth', { depth: depth + '%' });
- }
- }
- });
- }
- var scrollTick = false;
- window.addEventListener('scroll', function() {
- if (!scrollTick) {
- requestAnimationFrame(function() {
- trackScrollDepth();
- scrollTick = false;
- });
- scrollTick = true;
- }
- }, { passive: true });
- document.addEventListener('click', function(e) {
- var target = e.target.closest('a, button');
- if (!target) return;
- var eventData = {};
- if (target.tagName === 'A') {
- var href = target.getAttribute('href') || '';
- var text = (target.textContent || '').trim().slice(0, 40);
- if (href.startsWith('#')) {
- eventData = { type: 'anchor', target: href, text: text };
- } else if (href.startsWith('http') || href.startsWith('//')) {
- eventData = { type: 'outbound', target: href, text: text };
- } else {
- eventData = { type: 'internal', target: href, text: text };
- }
- if (target.getAttribute('target') === '_blank') {
- eventData.external = true;
- }
- }
- if (target.tagName === 'BUTTON') {
- eventData = { type: 'button', text: (target.textContent || '').trim().slice(0, 40) };
- }
- if (window.clarity && eventData.type) {
- window.clarity('event', 'cta_click', eventData);
- }
- });
- var DWELL_INTERVAL = 30000;
- var MAX_DWELL_BEATS = 10;
- var dwellCount = 0;
- var dwellTimer = setInterval(function() {
- dwellCount++;
- if (window.clarity) {
- window.clarity('event', 'dwell', { seconds: dwellCount * 30 });
- }
- if (dwellCount >= MAX_DWELL_BEATS) {
- clearInterval(dwellTimer);
- }
- }, DWELL_INTERVAL);
- if (window.clarity) {
- window.clarity('event', 'session_info', {
- width: screen.width,
- height: screen.height,
- referrer: document.referrer || '(direct)'
- });
- }
- })();
|