diff --git a/assets/css/main.css b/assets/css/main.css index d584067..894f1f7 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -130,14 +130,17 @@ a:focus-visible { position: relative; height: min(32.2vw, 617px); min-height: 430px; - background: #f4f8ff; + background: linear-gradient(90deg, #f7faff 0%, #e6eefc 100%); isolation: isolate; } .hero-art { position: absolute; z-index: -1; inset: 0; - background: url('/images/lanhu/slices/home-container25-image2@2x.png') center/cover no-repeat; + background-image: url('/images/lanhu/slices/hero-geo-visual@2x.png'); + background-repeat: no-repeat; + background-position: right center; + background-size: auto 100%; } .hero-content { height: 100%; @@ -272,6 +275,7 @@ a:focus-visible { text-align: center; } .section-title h2 { + position: relative; margin: 0; color: #414141; font-weight: 700; @@ -334,6 +338,7 @@ a:focus-visible { color: #0875f9; font-size: 34px; line-height: 1; + font-variant-numeric: tabular-nums; } .stat-item span { color: #555; @@ -342,40 +347,145 @@ a:focus-visible { } .model-section { + position: relative; min-height: 791px; padding: 76px 0 80px; + overflow: hidden; background: linear-gradient(135deg, #f8fbff 5%, #fff 44%, #eff4ff 100%); } +.model-section::before { + position: absolute; + right: -8%; + bottom: -42%; + left: -8%; + height: 72%; + content: ''; + pointer-events: none; + background: + radial-gradient(circle at 20% 64%, rgba(245, 195, 204, 0.4), transparent 31%), + radial-gradient(circle at 78% 66%, rgba(120, 224, 255, 0.45), transparent 35%); + filter: blur(22px); +} +.model-section > .page-width { + position: relative; +} .model-layout { display: grid; - grid-template-columns: 0.95fr 0.95fr 1.2fr; + grid-template-columns: minmax(280px, 370px) minmax(280px, 370px) minmax(310px, 1fr); align-items: center; - gap: 25px; - margin-top: 52px; + justify-content: center; + gap: 20px; + margin-top: 48px; } .ring { width: 100%; max-width: 370px; justify-self: center; + border: 0; + outline: 0; + box-shadow: none; + background: transparent; +} +.ring-diagram { + position: relative; + aspect-ratio: 1; + isolation: isolate; +} +.ring-diagram:focus, +.ring-diagram:focus-visible { + border: 0; + outline: 0; + box-shadow: none; +} +.ring-base { + width: 100%; + height: 100%; + display: block; + object-fit: contain; + border: 0; + outline: 0; + box-shadow: none; + background: transparent; +} +.ring-label { + position: absolute; + z-index: 2; + color: #4b83e8; + font-size: 12px; + font-weight: 600; + line-height: 1; + letter-spacing: 0.04em; + white-space: nowrap; + text-shadow: 0 1px 8px rgba(255, 255, 255, 0.95); +} +.ring-label-1 { + top: 27%; + left: 20%; + transform: rotate(-39deg); +} +.ring-label-2 { + top: 27%; + right: 18%; + transform: rotate(39deg); +} +.ring-label-3 { + right: 17%; + bottom: 25%; + transform: rotate(-39deg); +} +.ring-label-4 { + bottom: 25%; + left: 18%; + transform: rotate(39deg); +} +.ring-center-label { + position: absolute; + z-index: 2; + top: 50%; + left: 50%; + width: 36%; + display: grid; + justify-items: center; + gap: 8px; + color: #fff; + transform: translate(-50%, -45%); + text-align: center; + text-shadow: 0 2px 7px rgba(49, 83, 205, 0.26); +} +.ring-center-label svg { + width: 42px; + height: 42px; + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2.2; + filter: drop-shadow(0 4px 7px rgba(27, 79, 196, 0.22)); +} +.ring-center-label strong { + font-size: 16px; + font-weight: 600; + letter-spacing: 0.08em; + white-space: nowrap; } .core-value { - padding: 12px 0 0 10px; + padding: 6px 0 0 16px; } .core-value h3 { display: flex; align-items: center; gap: 13px; - margin: 0 0 17px; + margin: 0 0 12px; color: #1578fa; font-size: 21px; } .core-value h3 img { - width: 50px; - height: 50px; + width: 46px; + height: 46px; object-fit: contain; } .core-point { - padding: 15px 0 17px; + padding: 13px 0 14px; border-bottom: 1px dashed #d3d3d3; } .core-point h4 { @@ -393,13 +503,14 @@ a:focus-visible { background: linear-gradient(135deg, #a0e1ff, #3c67ff); } .core-point p { - margin: 10px 0 0; + max-width: 35em; + margin: 8px 0 0; color: #757575; - font-size: 16px; - line-height: 1.7; + font-size: 14px; + line-height: 1.65; } .core-value .primary-button { - margin-top: 34px; + margin-top: 25px; } .advantages-section { @@ -868,6 +979,72 @@ a:focus-visible { } } +@media (min-width: 761px) { + .site-header { + transition: + background 0.3s ease, + box-shadow 0.3s ease, + backdrop-filter 0.3s ease; + } + .site-header.scrolled { + background: rgba(255, 255, 255, 0.9); + box-shadow: 0 10px 30px rgba(68, 122, 196, 0.1); + backdrop-filter: blur(18px) saturate(1.16); + } + .hero-copy { + will-change: transform; + } + .section-title h2::after { + position: absolute; + right: 42%; + bottom: -13px; + left: 42%; + height: 2px; + content: ''; + border-radius: 2px; + background: linear-gradient(90deg, transparent, rgba(20, 120, 251, 0.78), transparent); + opacity: 0; + transform: scaleX(0); + transition: + transform 0.75s cubic-bezier(0.22, 1, 0.36, 1), + opacity 0.45s ease; + } + .section-title.is-visible h2::after { + opacity: 1; + transform: scaleX(1); + } + .motion-card { + --spot-x: 50%; + --spot-y: 50%; + --spot-opacity: 0; + position: relative; + isolation: isolate; + } + .motion-card::after { + position: absolute; + z-index: 3; + inset: 0; + content: ''; + pointer-events: none; + border-radius: inherit; + background: radial-gradient( + 280px circle at var(--spot-x) var(--spot-y), + rgba(71, 145, 255, 0.16), + transparent 58% + ); + box-shadow: inset 0 0 0 1px rgba(113, 169, 255, 0.24); + opacity: var(--spot-opacity); + } + .motion-card img { + will-change: transform; + } + .case-background, + .delivery-banner, + .testimonial-track { + will-change: transform; + } +} + @media (prefers-reduced-motion: reduce) { *, *::before, @@ -1017,21 +1194,10 @@ a:focus-visible { height: min(720px, calc(100svh - 55px)); min-height: 570px; } - .hero-section::after { - position: absolute; - z-index: -1; - inset: 0; - content: ''; - background: linear-gradient( - 90deg, - rgba(246, 250, 255, 0.98) 0%, - rgba(246, 250, 255, 0.9) 46%, - rgba(246, 250, 255, 0.15) 100% - ); - } .hero-art { - background-position: 66% center; - opacity: 0.86; + background-position: right bottom; + background-size: auto 74%; + opacity: 0.72; will-change: transform; } .hero-content { @@ -1141,6 +1307,21 @@ a:focus-visible { max-width: 220px; will-change: transform; } + .ring-label { + font-size: clamp(7px, 2.15vw, 9px); + letter-spacing: 0; + } + .ring-center-label { + gap: 4px; + } + .ring-center-label svg { + width: clamp(24px, 7vw, 32px); + height: clamp(24px, 7vw, 32px); + } + .ring-center-label strong { + font-size: clamp(9px, 2.7vw, 12px); + letter-spacing: 0.02em; + } .core-value { grid-column: 1 / -1; padding: 26px 0 0; diff --git a/pages/index.vue b/pages/index.vue index 662e767..287dbca 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -4,9 +4,12 @@ import { ScrollTrigger } from 'gsap/ScrollTrigger' const consultationOpen = ref(false) const mobileMenuOpen = ref(false) +const activeSection = ref('home') +const headerScrolled = ref(false) const notice = ref('') const pageRoot = ref(null) let animationContext +let animationMedia const navItems = [ ['首页', 'home'], @@ -39,8 +42,22 @@ const stats = [ ['65%', '客户转化链路效率平均提升', '/images/lanhu/slices/home-group103-image29@2x.png'] ] const corePoints = [ - ['曝光', '打造AI搜索语境下的品牌信任声量,一次布局 长期复用'], - ['获客', '流量,让每一分投入都有回报 让曝光直接落地为可跟进的精准线索,告别无效'] + ['曝光', '打造AI搜索语境下的品牌信任声量,一次布局,长期复用'], + ['获客', '让曝光直接沉淀为可跟进的精准线索,告别无效流量'] +] +const ringModels = [ + { + title: '声量资产环', + image: '/images/lanhu/slices/home-group80-group83-image11@2x.png', + icon: 'asset', + labels: ['真人内容生产', '全域媒体分发', 'AI信任构建', '品牌资产沉淀'] + }, + { + title: '获客转化环', + image: '/images/lanhu/slices/home-group86-group87-image12@2x.png', + icon: 'conversion', + labels: ['AI搜索曝光', '精准流量承接', '全链路转化', '数据复盘优化'] + } ] const advantageCards = [ ['全域媒体资源优势', '5000+权威官媒+10万+优质自媒体矩阵'], @@ -177,60 +194,228 @@ onMounted(async () => { reveal('.testimonial-track article', '.testimonial-track', { y: 22, stagger: 0.1 }) reveal('.cta-content', '.cta-section', { y: 26 }) - ScrollTrigger.matchMedia({ - '(max-width: 760px)': () => { - gsap.to('.logo-track', { - xPercent: -50, - duration: 24, - ease: 'none', - repeat: -1 - }) + animationMedia = gsap.matchMedia() - gsap.to('.ring:first-child', { - x: -12, - rotation: -2, - ease: 'none', - scrollTrigger: { - trigger: '.model-layout', - start: 'top bottom', - end: 'bottom top', - scrub: 0.8 + animationMedia.add('(max-width: 760px)', () => { + gsap.to('.logo-track', { + xPercent: -50, + duration: 24, + ease: 'none', + repeat: -1 + }) + + gsap.to('.ring:first-child', { + x: -12, + rotation: -2, + ease: 'none', + scrollTrigger: { + trigger: '.model-layout', + start: 'top bottom', + end: 'bottom top', + scrub: 0.8 + } + }) + + gsap.to('.ring:nth-child(2)', { + x: 12, + rotation: 2, + ease: 'none', + scrollTrigger: { + trigger: '.model-layout', + start: 'top bottom', + end: 'bottom top', + scrub: 0.8 + } + }) + + gsap.to('.cta-art', { + yPercent: -6, + ease: 'none', + scrollTrigger: { + trigger: '.cta-section', + start: 'top bottom', + end: 'bottom top', + scrub: 0.8 + } + }) + }) + + animationMedia.add('(min-width: 761px)', () => { + const cleanup = [] + const addListener = (element, event, handler) => { + element.addEventListener(event, handler) + cleanup.push(() => element.removeEventListener(event, handler)) + } + + ScrollTrigger.create({ + start: 80, + end: 'max', + onToggle: ({ isActive }) => { + headerScrolled.value = isActive + } + }) + + navItems.forEach(([, target]) => { + const trigger = target === 'home' ? '.hero-section' : `#${target}` + if (!document.querySelector(trigger)) return + ScrollTrigger.create({ + trigger, + start: 'top 48%', + end: 'bottom 48%', + onEnter: () => { + activeSection.value = target + }, + onEnterBack: () => { + activeSection.value = target } }) + }) - gsap.to('.ring:nth-child(2)', { - x: 12, - rotation: 2, - ease: 'none', - scrollTrigger: { - trigger: '.model-layout', - start: 'top bottom', - end: 'bottom top', - scrub: 0.8 - } + const hero = document.querySelector('.hero-section') + const heroCopy = document.querySelector('.hero-copy') + if (hero && heroCopy) { + const moveX = gsap.quickTo(heroCopy, 'x', { duration: 0.7, ease: 'power3.out' }) + const moveY = gsap.quickTo(heroCopy, 'y', { duration: 0.7, ease: 'power3.out' }) + addListener(hero, 'pointermove', (event) => { + const bounds = hero.getBoundingClientRect() + moveX(((event.clientX - bounds.left) / bounds.width - 0.5) * 16) + moveY(((event.clientY - bounds.top) / bounds.height - 0.5) * 10) }) - - gsap.to('.cta-art', { - yPercent: -6, - ease: 'none', - scrollTrigger: { - trigger: '.cta-section', - start: 'top bottom', - end: 'bottom top', - scrub: 0.8 - } + addListener(hero, 'pointerleave', () => { + moveX(0) + moveY(0) }) } + + document.querySelectorAll('.stat-item strong').forEach((element) => { + const finalText = element.textContent.trim() + const finalValue = Number.parseFloat(finalText) + const suffix = finalText.replace(/[\d.]/g, '') + const counter = { value: 0 } + ScrollTrigger.create({ + trigger: element, + start: 'top 88%', + once: true, + onEnter: () => { + gsap.to(counter, { + value: finalValue, + duration: 1.45, + ease: 'power2.out', + onUpdate: () => { + element.textContent = `${Math.round(counter.value)}${suffix}` + } + }) + } + }) + }) + + document.querySelectorAll('.section-title').forEach((title) => { + ScrollTrigger.create({ + trigger: title, + start: 'top 84%', + once: true, + onEnter: () => title.classList.add('is-visible') + }) + }) + + const interactiveCards = document.querySelectorAll( + '.stat-item, .advantage-grid article, .case-panel article, .industry-grid article, .delivery-grid article' + ) + interactiveCards.forEach((card) => { + card.classList.add('motion-card') + const image = card.querySelector('img') + const imageX = image + ? gsap.quickTo(image, 'x', { duration: 0.45, ease: 'power3.out' }) + : null + const imageY = image + ? gsap.quickTo(image, 'y', { duration: 0.45, ease: 'power3.out' }) + : null + addListener(card, 'pointermove', (event) => { + const bounds = card.getBoundingClientRect() + const x = event.clientX - bounds.left + const y = event.clientY - bounds.top + card.style.setProperty('--spot-x', `${x}px`) + card.style.setProperty('--spot-y', `${y}px`) + gsap.to(card, { '--spot-opacity': 1, duration: 0.2 }) + imageX?.((x / bounds.width - 0.5) * 9) + imageY?.((y / bounds.height - 0.5) * 9) + }) + addListener(card, 'pointerleave', () => { + gsap.to(card, { '--spot-opacity': 0, duration: 0.35 }) + imageX?.(0) + imageY?.(0) + }) + cleanup.push(() => card.classList.remove('motion-card')) + }) + + gsap.to('.model-layout .ring:first-child', { + x: -34, + rotation: -4, + ease: 'none', + scrollTrigger: { + trigger: '.model-layout', + start: 'top bottom', + end: 'bottom top', + scrub: 1 + } + }) + gsap.to('.model-layout .ring:nth-child(2)', { + x: 34, + rotation: 4, + ease: 'none', + scrollTrigger: { + trigger: '.model-layout', + start: 'top bottom', + end: 'bottom top', + scrub: 1 + } + }) + gsap.to('.case-background', { + xPercent: -4, + scale: 1.08, + ease: 'none', + scrollTrigger: { + trigger: '.cases-section', + start: 'top bottom', + end: 'bottom top', + scrub: 1 + } + }) + gsap.to('.delivery-banner', { + backgroundPosition: '50% 62%', + ease: 'none', + scrollTrigger: { + trigger: '.delivery-section', + start: 'top bottom', + end: 'bottom top', + scrub: 1 + } + }) + gsap.to('.testimonial-track', { + x: -720, + ease: 'none', + scrollTrigger: { + trigger: '.testimonial-section', + start: 'top bottom', + end: 'bottom top', + scrub: 1 + } + }) + + return () => cleanup.forEach((dispose) => dispose()) }) }, pageRoot.value) }) -onBeforeUnmount(() => animationContext?.revert()) +onBeforeUnmount(() => { + animationMedia?.revert() + animationContext?.revert() +})