From 31761f2f789fbc1b7f5c48726f043c5ee00d0633 Mon Sep 17 00:00:00 2001 From: 1 Date: Thu, 13 Aug 2026 12:55:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BA=E9=A6=96=E9=A1=B5=20V1=20=E5=92=8C=20?= =?UTF-8?q?V2=20=E5=A2=9E=E5=8A=A0=E9=AB=98=E7=BA=A7=E6=BB=9A=E5=8A=A8?= =?UTF-8?q?=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design-qa.md | 11 ++ src/TravelSite.css | 26 ++++ src/TravelSite.tsx | 301 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 336 insertions(+), 2 deletions(-) diff --git a/design-qa.md b/design-qa.md index ec406a6..6a7a3b7 100644 --- a/design-qa.md +++ b/design-qa.md @@ -32,4 +32,15 @@ Date: 2026-08-13 - P3: customer may prefer a different hero photograph after comparing all three versions; this is a content choice, not a layout defect. - P3: final English line breaks can be tuned after the customer confirms which direction to pursue. +## Motion pass — 2026-08-13 + +- V1 now has a high-impact entrance sequence, hero parallax, staggered business panels, alternating experience reveals, route-card wipes, team/photo entrances, gallery choreography, comparison-table reveal, and a closing CTA sequence. +- V2 now has a slower cinematic entrance, hero push-in and parallax, masked editorial-card reveals, feature-story image wipe, route and field-collage choreography, layered team-photo entrance, and a closing landscape push-in. +- Motion is scoped independently to each version; Classic remains unchanged. +- Browser timing inspection confirmed hero elements move from hidden/transformed states to their completed state in both versions. +- Scroll-trigger inspection confirmed V1 content bands and V2 editorial cards reveal and settle correctly. +- Browser console errors and warnings: 0 on both V1 and V2. +- Broken images: 0. Horizontal overflow at desktop: 0. +- Reduced-motion behavior is preserved: scripted movement is skipped and CSS animation duration is constrained. + final result: passed diff --git a/src/TravelSite.css b/src/TravelSite.css index cfeb1af..04cca30 100644 --- a/src/TravelSite.css +++ b/src/TravelSite.css @@ -54,6 +54,16 @@ .oc-v2-hero-scroll { position: absolute; right: 0; bottom: 0; width: 76px; height: 76px; display: grid; place-items: center; background: var(--v2-cyan); color: var(--v2-navy); } .oc-v2-hero-scroll svg { transform: rotate(90deg); } +.oc-home-v2 [style*="clip-path"], +.oc-home-v1 [style*="clip-path"] { will-change: clip-path, transform, opacity; } +.oc-v2-hero > img, +.oc-v2-experience-card > img, +.oc-v2-route-card img, +.oc-v2-field-collage img, +.oc-v2-final > img, +.oc-home-v1 .oc-experience-band img, +.oc-home-v1 .oc-home-safari-image { will-change: transform; } + .oc-v2-discover, .oc-v2-routes, .oc-v2-field { padding: clamp(100px, 11vw, 175px) clamp(24px, 5.5vw, 88px); } @@ -150,6 +160,22 @@ .oc-home-v2 :is(a, button):focus-visible { outline: 3px solid var(--v2-coral); outline-offset: 4px; } +@media (prefers-reduced-motion: reduce) { + .oc-home-v2 *, + .oc-home-v1 * { + animation-duration: .01ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + } + + .oc-home-v2 [style], + .oc-home-v1 [style] { + translate: none !important; + rotate: none !important; + scale: none !important; + } +} + @media (max-width: 960px) { .oc-v2-hero-copy { width: calc(100% - 48px); } .oc-v2-section-intro, diff --git a/src/TravelSite.tsx b/src/TravelSite.tsx index aba5fc7..7dd3747 100644 --- a/src/TravelSite.tsx +++ b/src/TravelSite.tsx @@ -2041,6 +2041,7 @@ function selectPage(path: string) { function EditorialHomePage() { const { content, language } = useTravelSite(); + const editorialRef = useRef(null); const home = content.home; const editorialExperiences = [ { number: '01', title: pickText('山野', 'Wild Karst', language), eyebrow: pickText('徒步 · 骑行 · 洞穴', 'Hike · Cycle · Caves', language), image: '/client-assets/karst-hike-ridge.jpg', href: '/experiences/karst-hiking/' }, @@ -2057,8 +2058,157 @@ function EditorialHomePage() { '/client-assets/culinary-long-table-banquet.jpg', ]; + useGSAP( + () => { + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + + const heroTimeline = gsap.timeline({ defaults: { ease: 'power3.out' } }); + heroTimeline + .from('.oc-v2-hero > img', { scale: 1.13, autoAlpha: 0.4, duration: 1.8, ease: 'power2.out' }) + .from('.oc-v2-hero-copy > p', { y: 20, autoAlpha: 0, duration: 0.6 }, '-=1.18') + .from('.oc-v2-hero h1', { y: 90, autoAlpha: 0, duration: 1.05 }, '-=.85') + .from('.oc-v2-hero-bottom > *', { y: 32, autoAlpha: 0, duration: 0.7, stagger: 0.12 }, '-=.58') + .from('.oc-v2-hero-scroll', { scale: 0.65, autoAlpha: 0, duration: 0.55 }, '-=.45'); + + gsap.to('.oc-v2-hero > img', { + yPercent: 10, + scale: 1.08, + ease: 'none', + scrollTrigger: { trigger: '.oc-v2-hero', start: 'top top+=90', end: 'bottom top', scrub: 0.7 }, + }); + + gsap.to('.oc-v2-hero-scroll svg', { + y: 8, + duration: 1.15, + repeat: -1, + yoyo: true, + ease: 'sine.inOut', + }); + + gsap.utils.toArray('.oc-v2-section-intro, .oc-v2-field-heading').forEach((heading) => { + gsap.from(heading.children, { + y: 58, + autoAlpha: 0, + duration: 0.9, + stagger: 0.12, + ease: 'power3.out', + scrollTrigger: { trigger: heading, start: 'top 78%', once: true }, + }); + }); + + gsap.from('.oc-v2-experience-card', { + y: 86, + clipPath: 'inset(0 0 100% 0)', + duration: 1.05, + stagger: 0.11, + ease: 'power4.out', + scrollTrigger: { trigger: '.oc-v2-experience-grid', start: 'top 76%', once: true }, + }); + gsap.from('.oc-v2-experience-card > img', { + scale: 1.18, + duration: 1.5, + stagger: 0.11, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-v2-experience-grid', start: 'top 76%', once: true }, + }); + + gsap.from('.oc-v2-feature-image', { + clipPath: 'inset(0 100% 0 0)', + duration: 1.25, + ease: 'power4.inOut', + scrollTrigger: { trigger: '.oc-v2-feature', start: 'top 72%', once: true }, + }); + gsap.from('.oc-v2-feature-image img', { + scale: 1.16, + duration: 1.6, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-v2-feature', start: 'top 72%', once: true }, + }); + gsap.from('.oc-v2-feature-copy > *', { + y: 58, + autoAlpha: 0, + duration: 0.82, + stagger: 0.1, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-v2-feature-copy', start: 'top 74%', once: true }, + }); + + gsap.from('.oc-v2-route-card', { + y: 90, + clipPath: 'inset(100% 0 0 0)', + duration: 1.08, + stagger: 0.13, + ease: 'power4.out', + scrollTrigger: { trigger: '.oc-v2-route-grid', start: 'top 76%', once: true }, + }); + gsap.from('.oc-v2-route-card img', { + scale: 1.16, + duration: 1.55, + stagger: 0.13, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-v2-route-grid', start: 'top 76%', once: true }, + }); + + gsap.from('.oc-v2-field-collage figure, .oc-v2-field-note', { + y: 76, + scale: 0.96, + autoAlpha: 0, + duration: 0.9, + stagger: 0.1, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-v2-field-collage', start: 'top 76%', once: true }, + }); + gsap.to('.oc-v2-field-collage figure.is-1 img', { + yPercent: 10, + scale: 1.08, + ease: 'none', + scrollTrigger: { trigger: '.oc-v2-field-collage', start: 'top bottom', end: 'bottom top', scrub: 0.8 }, + }); + + gsap.from('.oc-v2-local-media img:first-child', { + clipPath: 'inset(0 0 100% 0)', + scale: 1.1, + duration: 1.25, + ease: 'power4.out', + scrollTrigger: { trigger: '.oc-v2-local', start: 'top 72%', once: true }, + }); + gsap.from('.oc-v2-local-media img:last-child', { + x: 70, + y: 60, + rotate: 2, + autoAlpha: 0, + duration: 0.95, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-v2-local', start: 'top 65%', once: true }, + }); + gsap.from('.oc-v2-local-copy > *', { + y: 52, + autoAlpha: 0, + duration: 0.78, + stagger: 0.09, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-v2-local-copy', start: 'top 74%', once: true }, + }); + + gsap.from('.oc-v2-final > div > *', { + y: 66, + autoAlpha: 0, + duration: 0.9, + stagger: 0.13, + ease: 'power3.out', + scrollTrigger: { trigger: '.oc-v2-final', start: 'top 66%', once: true }, + }); + gsap.fromTo('.oc-v2-final > img', { scale: 1.06 }, { + scale: 1.16, + ease: 'none', + scrollTrigger: { trigger: '.oc-v2-final', start: 'top bottom', end: 'bottom top', scrub: 0.9 }, + }); + }, + { scope: editorialRef, dependencies: [language], revertOnUpdate: true }, + ); + return ( -
+
{pickText('贵州喀斯特山野中的万趣私家旅行',