feat(landing): full-page scroll-reactive 3D background, mobile fixes & snapshot tests#64
Conversation
… + snapshot tests Landing page overhaul: - Replace the two isolated hero/CTA canvases with ONE persistent full-page 3D scene (position: sticky) whose camera dollies down through a vertical column of frosted-glass panels as the page scrolls, framing a glowing core at both the hero (top) and CTA (bottom). Driven by a scroll-progress ref so scrolling never re-renders React. - Cohesive dark theme: content surfaces are now SOLID dark panels (opaque, not see-through) so cards/accordions stay crisp, while the 3D shows through the open areas (hero, CTA, section gaps). - Mobile: remove the floating scroll-cue arrow that collided with the stats card; the stats band no longer overlaps the hero on small screens. - Hero CTA row: "Take the tour" gains a PlayCircleOutline icon and a new "Scroll to explore" text button (bouncing ArrowDownward) replaces the arrow. - Honors prefers-reduced-motion and keeps the WebGL/low-power fallbacks. Testing: - Add comprehensive Jest snapshot coverage (11 snapshots) for the chrome, static pages, auth pages, and the redesigned LandingPage (WebGL hero and WebAuthn helpers mocked for determinism). - Add jsdom polyfills (matchMedia, Intersection/Resize observers, scroll helpers) in setupTests and a renderWithRouter test helper. All 46 tests / 11 snapshots pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for docuthinker-ai-app canceled.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
There was a problem hiding this comment.
Code Review
This pull request implements a dark, cinematic full-page sticky 3D background on the landing page that dynamically responds to scroll progress. It also introduces snapshot tests for various pages and components, along with necessary jsdom polyfills and test utilities. The code review feedback suggests simplifying the scroll reference check using optional chaining and nullish coalescing, replacing dynamic array index keys with stable floatSeed keys for panels and accents to prevent rendering issues, and replacing a hardcoded color with a theme palette token.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const p = reduceMotion | ||
| ? 0 | ||
| : THREE.MathUtils.clamp( | ||
| scrollRef && scrollRef.current ? scrollRef.current : 0, | ||
| 0, | ||
| 1, | ||
| ); |
There was a problem hiding this comment.
The ternary and logical expression used to safely access scrollRef.current can be simplified and made more idiomatic using modern JavaScript optional chaining (?.) and nullish coalescing (??). This improves readability and maintainability.
const p = reduceMotion
? 0
: THREE.MathUtils.clamp(scrollRef?.current ?? 0, 0, 1);| {panels.map((p, i) => ( | ||
| <Panel key={i} reduceMotion={reduceMotion} lowPower={lowPower} {...p} /> | ||
| ))} |
There was a problem hiding this comment.
Using the array index i as a key is an anti-pattern here because the panels list is dynamically filtered based on the lowPower prop. When lowPower is active, the list is filtered, shifting the indices of the remaining elements. This causes React to mismatch component instances, leading to unnecessary re-renders and potential visual jumps in the floating animations.
Since each panel has a unique and stable floatSeed value, we can use it as a stable key instead.
| {panels.map((p, i) => ( | |
| <Panel key={i} reduceMotion={reduceMotion} lowPower={lowPower} {...p} /> | |
| ))} | |
| {panels.map((p) => ( | |
| <Panel key={p.floatSeed} reduceMotion={reduceMotion} lowPower={lowPower} {...p} /> | |
| ))} |
| {accents.map((a, i) => ( | ||
| <Accent key={i} reduceMotion={reduceMotion} {...a} /> | ||
| ))} |
There was a problem hiding this comment.
Similar to the panels list, using the array index i as a key for accents causes issues when the list is filtered under lowPower mode.
Using the unique and stable floatSeed property as the key ensures React correctly identifies and preserves the state of each Accent component.
| {accents.map((a, i) => ( | |
| <Accent key={i} reduceMotion={reduceMotion} {...a} /> | |
| ))} | |
| {accents.map((a) => ( | |
| <Accent key={a.floatSeed} reduceMotion={reduceMotion} {...a} /> | |
| ))} |
| p: { xs: 3, md: 6 }, | ||
| borderRadius: "28px", | ||
| backgroundColor: palette.heroBg, | ||
| backgroundColor: "#0f0a05", |
There was a problem hiding this comment.
Avoid using hardcoded color values like "#0f0a05" directly in the component styling. Instead, use a theme palette token (such as palette.surfaceMuted or palette.background) to maintain visual consistency and ensure that any future theme updates propagate correctly.
| backgroundColor: "#0f0a05", | |
| backgroundColor: palette.surfaceMuted, |
Captured at laptop viewport (1512×834 @2x = 3024×1668) to match the existing asset, showing the full-page 3D hero and the new CTA row (Start for free · Take the tour · Scroll to explore). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- README: add Three.js (logo: threedotjs) and React Three Fiber (logo: reactos) tech badges; list Three.js / R3F / Drei in the frontend stack; describe the scroll-reactive 3D landing experience under the screenshot. - ARCHITECTURE: new "Landing Page 3D Experience" section (+ TOC entry) covering the sticky-canvas/negative-margin compositing, scroll-driven camera, procedural scene, capability detection/fallbacks, and how the snapshot suite mocks WebGL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve conflict in frontend/src/setupTests.js: keep master's jsdom polyfill block (matchMedia, scrollTo, scrollIntoView, Intersection/Resize observers) and append the autoFocus neutralization the snapshot suite needs. master's PR #64 already added snapshot coverage for the static pages, auth pages, app chrome, and the redesigned 3D LandingPage (07_snapshots.test.js). To avoid duplicating it, this branch now only keeps snapshot suites for the data-heavy pages #64 deliberately excluded — Home, Documents, Profile, and Passkeys — which render their deterministic initial/loading state. The overlapping per-screen files and the redundant inline-Babel jest.config change are dropped (a babel.config.js from #64 already transforms JSX). Docs updated to describe both snapshot suites. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Builds on the recent cinematic 3D landing overhaul. The 3D background is now a single, continuous, scroll-reactive scene spanning the whole page (it was previously boxed into just the hero and CTA), plus mobile responsiveness fixes and a new Jest snapshot suite.
Landing page
position: stickycanvas pinned to the viewport for the entire scroll. Its camera dollies down through a vertical column of frosted-glass panels as you scroll, framing a glowing core at both the hero (top) and CTA (bottom). Scroll progress is held in a ref and read in the render loop, so scrolling never triggers React re-renders.Take the tourgains aPlayCircleOutlineicon; a newScroll to exploretext button (bouncingArrowDownward) replaces the old floating arrow and scrolls to the content below.prefers-reduced-motionand keeps the existing WebGL / low-power fallbacks.Testing
07_snapshots.test.js, 11 snapshots): chrome (Navbar/Footer/Spinner), static pages (NotFound/Privacy/Terms/HowToUse), auth pages (Login/Register/ForgotPassword), and the redesigned LandingPage. The WebGL hero and WebAuthn helpers are mocked for determinism; heavy runtime-only modals are intentionally excluded.matchMedia, Intersection/Resize observers, scroll helpers) tosetupTests.jsand arenderWithRouterhelper.Results:
production build compiles cleanly· 46/46 tests pass · 11/11 snapshots (stable across re-runs).Notes
frontend/touched; backend/orchestrator untouched.npx jest -u.🤖 Generated with Claude Code