Skip to content

feat(home): premium homepage redesign with procedural 3D hero - #36

Merged
hoangsonww merged 2 commits into
masterfrom
feat/home-3d-redesign
Jun 13, 2026
Merged

feat(home): premium homepage redesign with procedural 3D hero#36
hoangsonww merged 2 commits into
masterfrom
feat/home-3d-redesign

Conversation

@hoangsonww

Copy link
Copy Markdown
Owner

Summary

Complete redesign of the storefront homepage — a premium, professional, eye-catching landing page with a procedural 3D hero, while keeping all product/recommendation/newsletter functionality intact.

3D hero (src/components/HeroScene.jsx)

  • Procedural react-three-fiber backdrop: glossy metallic torus-knot + floating tech shapes (brand blue/indigo/pink/sky), inline Lightformer reflections, sparkle field — no .glb/.hdr/image assets.
  • Cursor-reactive parallax.
  • Lazy-loaded; GPU-tier gated with reduced-motion / WebGL-failure / context-loss fallbacks → weak devices keep the CSS gradient hero. three/drei sit in a lazy chunk.

Home redesign (src/pages/Home.jsx)

  • Dark cinematic hero: white headline + gradient accent word, glass trust card, stat row, gradient CTAs.
  • Gradient value-prop cards, centered/enlarged category cards, refreshed testimonials, newsletter band.
  • Replaced the image carousel with a clean CSS spotlight promo band.
  • Infinite brand marquee (seamless), soft page aurora, one-shot entrance animation (never leaves content hidden).

Shared ProductCard

  • White image area so product photos blend, divider between media and content, hover image-zoom + lift, gradient add-to-cart CTA.

Footer

  • Insider-List form is now single-row (input + send button inline).

Notes

  • Contrast bug fixed (global h1,h2,h3{color:#333} was overriding the hero's white text).
  • Iterated against the running app with Playwright (desktop + mobile verified).

Testing

  • jest78 passed / 30 suites. Updated Home + Footer snapshots; removed the obsolete banner test.
  • npm run build compiles (only the pre-existing @mediapipe source-map warning).

🤖 Generated with Claude Code

- Add a procedural react-three-fiber hero backdrop (HeroScene): glossy
  torus-knot + floating tech shapes, cursor parallax, inline Lightformer
  reflections. Lazy-loaded, GPU-tier gated, with reduced-motion / WebGL
  fallbacks so weak devices keep the CSS gradient hero.
- Redesign Home: dark cinematic hero (white headline + gradient accent,
  glass trust card, stats), gradient value-prop and category cards, a clean
  CSS spotlight promo band (replaces the image carousel), refreshed
  testimonials and newsletter, an infinite brand marquee, and a soft page
  aurora. Light one-shot entrance animation (never leaves content hidden).
- Polish shared ProductCard: white image area so product photos blend, a
  divider between media and content, image zoom on hover, stronger lift, and
  a gradient add-to-cart CTA.
- Make the footer Insider-List form single-row (input + send inline).
- Update Home and Footer snapshots; drop the obsolete banner test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
mern-stack-ecommerce-app Ignored Ignored Jun 13, 2026 7:22pm

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a home page redesign, replacing the image carousel with a procedural 3D backdrop (HeroScene) built with React Three Fiber for capable GPUs, alongside UI enhancements to the product cards, footer, and brand marquee. The review feedback highlights three key improvements: explicitly losing the temporary WebGL context during capability testing in Home.jsx to prevent GPU resource leaks, using a mutable ref for the onLost callback in HeroScene.jsx to avoid stale closures, and explicitly mocking the default export of HeroScene in Home.test.js to ensure compatibility with React.lazy.

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.

Comment thread src/pages/Home.jsx
Comment on lines +70 to +77
let gl = null;
try {
const c = document.createElement('canvas');
gl = c.getContext('webgl2') || c.getContext('webgl') || c.getContext('experimental-webgl');
} catch (e) {
gl = null;
}
if (!gl) return 'none';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When performing WebGL capability testing, creating a WebGL context allocates GPU resources. If these temporary contexts are not explicitly cleaned up, they can linger and cause context leaks, eventually hitting the browser's limit on active WebGL contexts. It is highly recommended to explicitly lose the temporary context using the WEBGL_lose_context extension once the capability check is complete.

Suggested change
let gl = null;
try {
const c = document.createElement('canvas');
gl = c.getContext('webgl2') || c.getContext('webgl') || c.getContext('experimental-webgl');
} catch (e) {
gl = null;
}
if (!gl) return 'none';
let gl = null;
try {
const c = document.createElement('canvas');
gl = c.getContext('webgl2') || c.getContext('webgl') || c.getContext('experimental-webgl');
} catch (e) {
gl = null;
}
if (!gl) return 'none';
const loseContextExt = gl.getExtension('WEBGL_lose_context');
if (loseContextExt) {
loseContextExt.loseContext();
}

Comment on lines +96 to +117
const HeroScene = ({ quality = 'mid', onLost }) => {
const pointerRef = useRef({ x: 0, y: 0 });
useEffect(() => {
if (typeof window === 'undefined') return undefined;
const onMove = e => {
pointerRef.current = {
x: (e.clientX / window.innerWidth) * 2 - 1,
y: -((e.clientY / window.innerHeight) * 2 - 1),
};
};
window.addEventListener('pointermove', onMove, { passive: true });
return () => window.removeEventListener('pointermove', onMove);
}, []);

return (
<Canvas
dpr={[1, quality === 'high' ? 1.8 : 1.3]}
camera={{ position: [0, 0, 8], fov: 42 }}
gl={{ antialias: quality === 'high', alpha: true, powerPreference: 'high-performance', failIfMajorPerformanceCaveat: false }}
onCreated={({ gl }) => {
gl.domElement.addEventListener('webglcontextlost', e => { e.preventDefault(); if (onLost) onLost(); }, { once: true });
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The onLost callback is registered inside the onCreated listener of the Canvas, which only runs once during initialization. Since onLost is passed as an inline arrow function from the parent Home component, it changes on every render. This creates a stale closure bug where the event listener will always call the initial render's version of onLost. To prevent this, store onLost in a mutable ref and call the ref's current value inside the event listener.

const HeroScene = ({ quality = 'mid', onLost }) => {
  const pointerRef = useRef({ x: 0, y: 0 });
  const onLostRef = useRef(onLost);
  useEffect(() => {
    onLostRef.current = onLost;
  }, [onLost]);

  useEffect(() => {
    if (typeof window === 'undefined') return undefined;
    const onMove = e => {
      pointerRef.current = {
        x: (e.clientX / window.innerWidth) * 2 - 1,
        y: -((e.clientY / window.innerHeight) * 2 - 1),
      };
    };
    window.addEventListener('pointermove', onMove, { passive: true });
    return () => window.removeEventListener('pointermove', onMove);
  }, []);

  return (
    <Canvas
      dpr={[1, quality === 'high' ? 1.8 : 1.3]}
      camera={{ position: [0, 0, 8], fov: 42 }}
      gl={{ antialias: quality === 'high', alpha: true, powerPreference: 'high-performance', failIfMajorPerformanceCaveat: false }}
      onCreated={({ gl }) => {
        gl.domElement.addEventListener('webglcontextlost', e => { e.preventDefault(); if (onLostRef.current) onLostRef.current(); }, { once: true });
      }}

Comment thread src/tests/Home.test.js
jest.mock('../assets/images/tech-gadgets.jpg', () => 'tech.jpg');
jest.mock('../assets/images/trending-fashion.jpg', () => 'fashion.jpg');
// Mock the 3D hero so tests never touch WebGL / three
jest.mock('../components/HeroScene', () => () => <div data-testid="hero-scene" />);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since HeroScene is lazy-loaded using React.lazy, it expects the resolved module to have a .default property containing the component. Mocking the module directly as a function can cause runtime errors during tests depending on the test environment's module interop settings. Explicitly mock the default export to ensure robust compatibility with React.lazy.

jest.mock('../components/HeroScene', () => ({
  __esModule: true,
  default: () => <div data-testid="hero-scene" />,
}));

@hoangsonww hoangsonww self-assigned this Jun 13, 2026
@hoangsonww hoangsonww added bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request help wanted Extra attention is needed good first issue Good for newcomers question Further information is requested labels Jun 13, 2026
@hoangsonww hoangsonww added this to the v1.x.x - Stable Release milestone Jun 13, 2026
@hoangsonww
hoangsonww changed the base branch from feat/frontend-snapshot-tests to master June 13, 2026 19:22
@netlify

netlify Bot commented Jun 13, 2026

Copy link
Copy Markdown

Deploy Preview for mern-stack-ecommerce-website ready!

Name Link
🔨 Latest commit 2b0b8de
🔍 Latest deploy log https://app.netlify.com/projects/mern-stack-ecommerce-website/deploys/6a2dae1032f45b000820db65
😎 Deploy Preview https://deploy-preview-36--mern-stack-ecommerce-website.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 38
Accessibility: 88
Best Practices: 100
SEO: 100
PWA: 80
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@hoangsonww
hoangsonww merged commit b4811d1 into master Jun 13, 2026
18 checks passed
@hoangsonww hoangsonww moved this from Done to Ready in Fusion Electronics MERN Project Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed question Further information is requested

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

1 participant