/** * 亿图腾 · 浠艾福 — 企业官网交互脚本 * 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 = '

暂无内容,敬请期待

'; 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 = '' + relatedDim + ''; } else if (relatedDim) { dimTag = '' + relatedDim + ''; } var coverHtml = article.coverImage ? '' + article.title + '' : '
'; var catName = article.categoryName || ''; html += '
' + coverHtml + '
' + (catName ? '' + catName + '' : '') + '

' + article.title + '

' + (article.summary ? '

' + article.summary + '

' : '') + '
' + dimTag + (article.readTime ? '阅读约 ' + article.readTime + ' 分钟' : '') + '
'; }); grid.innerHTML = html; }) .catch(function () { if (loading) loading.style.display = 'none'; grid.innerHTML = '

暂时无法加载,请稍后再试

'; }); })(); // -------------------------------------------------------- // 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)' }); } })();