diff --git a/packages/twenty-website/src/sections/product-hero/HeroVisualScroll.tsx b/packages/twenty-website/src/sections/product-hero/HeroVisualScroll.tsx index 9b3820aa92..e7c49d16eb 100644 --- a/packages/twenty-website/src/sections/product-hero/HeroVisualScroll.tsx +++ b/packages/twenty-website/src/sections/product-hero/HeroVisualScroll.tsx @@ -214,6 +214,12 @@ const HeadingSlot = styled.div` position: relative; width: 100%; + ${mediaUp('sm')} { + &[data-measure='ai'] { + max-width: 560px; + } + } + ${mediaUp('md')} { max-width: 672px; } @@ -648,7 +654,7 @@ export function HeroVisualScroll({ - + {aiHeading} diff --git a/packages/twenty-website/src/sections/product-hero/product-hero-scroll-model.test.ts b/packages/twenty-website/src/sections/product-hero/product-hero-scroll-model.test.ts index f152d76652..03501b204d 100644 --- a/packages/twenty-website/src/sections/product-hero/product-hero-scroll-model.test.ts +++ b/packages/twenty-website/src/sections/product-hero/product-hero-scroll-model.test.ts @@ -30,11 +30,13 @@ describe('computeHeroScrollModel', () => { it('should complete the morph at 55% of the scrollable run', () => { const model = atScroll(0.55); expect(model.morphProgress).toBe(1); - expect(model.aiPanelProgress).toBe(1); - expect(model.aiPlaybackEnabled).toBe(true); expect(model.selectorRevealReady).toBe(true); expect(model.stackSpreadProgress).toBe(1); expect(model.stackSpreadEasedProgress).toBe(1); + // The Ask-AI panel is deliberately still closed here; it reveals only + // in the post-morph hold (see the panel-timing test below). + expect(model.aiPanelProgress).toBe(0); + expect(model.aiPlaybackEnabled).toBe(false); }); it('should ease the morph through smoothstep', () => { @@ -51,7 +53,19 @@ describe('computeHeroScrollModel', () => { expect(half.stackAppearProgress).toBeCloseTo((0.5 - 0.4) / 0.16, 10); expect(half.stackAlignProgress).toBe(0); expect(half.stackSpreadProgress).toBe(0); - expect(half.aiPanelProgress).toBeCloseTo((0.5 - 0.45) / 0.25, 10); + }); + + it('should hold the Ask-AI panel back until the post-morph hold', () => { + // The wipe finishes at scroll 0.55; the panel reveals over [0.60, 0.70] + // of raw scroll progress, then holds open, and playback begins only + // once it has fully opened. + expect(atScroll(0.55).aiPanelProgress).toBe(0); + expect(atScroll(0.55).aiPlaybackEnabled).toBe(false); + expect(atScroll(0.65).aiPanelProgress).toBeCloseTo(0.5, 10); + expect(atScroll(0.65).aiPlaybackEnabled).toBe(false); + expect(atScroll(0.7).aiPanelProgress).toBe(1); + expect(atScroll(0.7).aiPlaybackEnabled).toBe(true); + expect(atScroll(1).aiPanelProgress).toBe(1); }); it('should gate the AI layer at the wipe midpoint', () => { diff --git a/packages/twenty-website/src/sections/product-hero/product-hero-scroll-model.ts b/packages/twenty-website/src/sections/product-hero/product-hero-scroll-model.ts index f45c52ad1e..0163eb3122 100644 --- a/packages/twenty-website/src/sections/product-hero/product-hero-scroll-model.ts +++ b/packages/twenty-website/src/sections/product-hero/product-hero-scroll-model.ts @@ -40,6 +40,14 @@ const NAV_HEIGHT_PX = 64; const MORPH_START = 0; const MORPH_END = 0.55; +// The Ask-AI panel holds back until the wipe has fully risen, then slides +// in during the post-morph hold as its own beat — so the eye, having +// followed the black edge up, lands on the panel instead of competing +// with it. Keyed to raw scroll progress: morph is pinned at 1 through +// this whole stretch and so can't time anything past MORPH_END. +const AI_PANEL_REVEAL_START = 0.6; +const AI_PANEL_REVEAL_END = 0.7; + function smoothstep(value: number): number { return value * value * (3 - 2 * value); } @@ -102,8 +110,11 @@ export function computeHeroScrollModel({ const selectorRevealProgress = clampProgress((morphProgress - 0.94) / 0.06); return { - aiPanelProgress: clampProgress((morphProgress - 0.45) / 0.25), - aiPlaybackEnabled: morphProgress >= 0.7, + aiPanelProgress: clampProgress( + (progress - AI_PANEL_REVEAL_START) / + (AI_PANEL_REVEAL_END - AI_PANEL_REVEAL_START), + ), + aiPlaybackEnabled: progress >= AI_PANEL_REVEAL_END, aiPointerEventsEnabled: morphProgress > 0.5, heroAtStart: morphProgress <= 0, introCursorsActive: morphProgress < 0.5,