public 3d-motion repository

MacBook--Showcase-Landing-Page--React-Frontend

// arnobt78/MacBook--Showcase-Landing-Page--React-Frontend

A single-page, client-side marketing-style experience inspired by Apple product pages. It combines scroll-driven storytelling (GSAP), interactive 3D (Three.js via React Three Fiber), and a small global state slice (Zustand) so learners can see how modern landing pages mix layout, motion, and WebGL without a traditional backend or REST API

$ git log --oneline --stat
stars:3forks:0updated:2026-04-17
README.md
readonly

MacBook Showcase Landing Page - React, Vite, TypeScript, Tailwind CSS, GSAP, Three.js, Zustand Frontend Project

License: MIT Vite React TypeScript Tailwind CSS GSAP Three.js React Three Fiber Zustand

A single-page application (SPA)—one HTML shell, one React tree, no server-rendered pages and no built-in REST or GraphQL API. It is built for learning and portfolio demos: you can study how a marketing-style landing page combines layout (Tailwind), time-based and scroll-linked motion (GSAP), viewport-driven CSS reveals (IntersectionObserver), and interactive 3D (Three.js through React Three Fiber) in one cohesive codebase. All marketing copy, nav targets, and asset paths are static (see src/constants/index.ts and public/). Extend it with a backend or CMS when your product outgrows hardcoded data.

Live demo: https://macbook-ui.vercel.app/

Image 1 Image 2 Image 3 Image 4 Image 5 Image 6

Table of contents

  1. What you will learn
  2. Keywords at a glance
  3. Tech stack & dependencies
  4. Architecture at a glance
  5. Project structure
  6. Routing, pages & “API”
  7. Features & how each part works
  8. Environment variables (.env)
  9. How to run & npm scripts
  10. Build, preview & deploy
  11. Reusing components in other projects
  12. Styling & motion docs
  13. Linting & type-checking
  14. Further reading
  15. Conclusion
  16. License & closing

What you will learn

  • How Vite boots a React + TypeScript SPA and how index.html relates to src/main.tsx.
  • How GSAP and ScrollTrigger map scroll position to timelines (scrub, pin, stagger).
  • How @gsap/react useGSAP ties animations to refs with safer cleanup than raw useEffect for many GSAP cases.
  • How React Three Fiber embeds a Three.js scene in JSX and loads GLB/GLTF models with drei helpers (useGLTF, useVideoTexture, Environment, etc.).
  • How Zustand shares a tiny slice of state (color, scale, texture) between DOM controls and materials in the 3D tree.
  • How Tailwind CSS v4 with @tailwindcss/vite and @layer components in src/index.css scopes section layout to IDs like #hero, #features.
  • How IntersectionObserver + CSS transitions implement “reveal on viewport” patterns without GSAP for some sections (footer copy rows, performance paragraph lines, hero stagger).

Keywords at a glance

Keyword / topicShort meaning in this repo
SPAOne index.html; all “navigation” is in-page (#section hashes) or scroll—not Next.js file routes.
ScrollTriggerGSAP plugin: run or scrub timelines based on scroll range vs. a trigger element.
ScrubAnimation progress is locked to scroll (drag scroll = drag timeline).
PinScrollTrigger can fix a section while the user scrolls through a longer virtual range (Showcase / Features).
R3FReact renderer for Three: <Canvas>, hooks like useFrame, tree = scene graph.
dreiHelpers on top of R3F: lights, loaders, Html, controls, textures.
GLBBinary glTF; 3D MacBook meshes live under public/models/.
ZustandSmall global store; no Redux boilerplate.
import.meta.envVite’s way to read env vars (only if you add VITE_* keys later).
public/Files served at root URL (/videos/hero.mp4).

Tech stack & dependencies

PackageRoleLearner note
viteDev server, HMR, production build.vite.config.js splits three, drei, fiber, gsap into separate chunks for caching.
@vitejs/plugin-reactJSX + Fast Refresh.Standard React plugin for Vite.
react / react-domUI layer.Entry: src/main.tsxApp.
typescriptStatic types.tsconfig.json + tsconfig.app.json for app vs Node tooling.
tailwindcss + @tailwindcss/viteUtility CSS + Vite integration (v4).No separate PostCSS config required for this setup.
gsap + @gsap/reactAnimation + React hook useGSAP.ScrollTrigger registered once in App.tsx.
three + @react-three/fiber + @react-three/dreiWebGL scene in React.Canvas in ProductViewer / Features.
zustandGlobal client store.src/store/index.ts—no persistence.
react-responsiveuseMediaQuery for breakpoints.Used to skip heavy desktop-only ScrollTrigger setups on small screens.
clsxConditional class strings.e.g. active color swatch in ProductViewer.

Example: reading the Zustand store in a component

import useMacbookStore from "./store";

const { color, setColor } = useMacbookStore();
// Pass `color` into a GLTF material hook or UI className

Example: registering GSAP once (already in App.tsx)

import gsap from "gsap";
import { ScrollTrigger } from "gsap/all";

gsap.registerPlugin(ScrollTrigger);

Architecture at a glance

index.html (shell, SEO, critical CSS, preloads)
    └── main.tsx (React root)
            └── App.tsx (ScrollTrigger register + scroll reset + <main> sections)
                    ├── NavBar, Hero, ProductViewer, …
                    ├── R3F <Canvas> where used (WebGL context per canvas)
                    └── constants/index.ts (static data, no fetch)
  • No backend in this repo: nothing listens on a port for JSON. “Data” = TypeScript arrays + static files.
  • Vercel: vercel.json rewrites all paths to index.html so client-side navigation and refresh on deep links work for the SPA.

Project structure

macbook-ui/
├── public/                    # Static assets (URLs start with /)
│   ├── fonts/
│   ├── models/                # .glb MacBook variants
│   ├── videos/                # hero, game, feature loops
│   ├── robots.txt
│   └── …images / svg
├── src/
│   ├── main.tsx               # createRoot + StrictMode
│   ├── App.tsx                # ScrollTrigger + section order + scroll restoration
│   ├── index.css              # Tailwind + @layer components (#hero, #features, …)
│   ├── vite-env.d.ts          # Vite client typings
│   ├── constants/index.ts     # Nav, features, performance layout data, footer links
│   ├── store/index.ts         # Zustand: color, scale, texture
│   ├── types/macbookGltf.ts   # GLTF typing helper
│   └── components/
│       ├── NavBar.tsx
│       ├── Hero.tsx
│       ├── ProductViewer.tsx
│       ├── Showcase.tsx
│       ├── Performance.tsx
│       ├── Features.tsx
│       ├── Highlights.tsx
│       ├── Footer.tsx
│       ├── models/            # Macbook GLTF JSX wrappers
│       └── three/             # StudioLights, ModelSwitcher
├── docs/                      # Extra guides (styling, parallax, deployment notes)
├── index.html                 # Entry HTML, meta, preloads, hero CTA anti-flash
├── vite.config.js             # React + Tailwind plugins, manualChunks
├── vercel.json                # SPA fallback rewrite → index.html
├── eslint.config.js
├── tsconfig*.json
├── LICENSE                    # MIT
└── README.md                  # This file

Routing, pages & “API”

TopicIn this project
FrameworkNot Next.js—there is no pages/ or app/ router.
RoutesThere is one page. “Sections” are <section id="…"> blocks; navbar links use href="#hero" etc. (see constants.navLinks).
REST / GraphQLNone. No fetch to a backend in the shipped demo.
Datasrc/constants/index.ts + files under public/.
SSRNo server components; build output is static JS + CSS + assets.

If you add a real API later, you would introduce something like VITE_API_BASE_URL, use fetch or a data library (TanStack Query), and keep secrets out of the client bundle.


Features & how each part works

1. NavBar

Fixed header; logo scrolls to top; center links jump to section IDs (scroll-padding-top in CSS clears the fixed bar). Entrance uses a short GSAP tween on <nav>.

2. Hero

Above-the-fold headline, title image, muted autoplay video, CTA, price. Uses CSS transitions + IntersectionObserver for staggered reveal; separate observer for the video shell (restarts clip on enter). data-hero-cta-ready on #root pairs with index.html to avoid a flashing Buy button before JS runs.

3. ProductViewer

Zustand drives finish color and 14″/16″ scale; R3F <Canvas> renders ModelSwitcher (two GLB groups) with PresentationControls and idle rotation helper. No API—state is local to the browser.

4. Showcase

Background video + masked logo. Desktop: ScrollTrigger pin + scrub timeline; tablet/small timeline skipped for layout reasons. Copy blocks use IntersectionObserver + .showcase-row CSS stagger.

5. Performance

Desktop: scrubbed timeline moves .p1.p7 collage images (see performanceImgPositions in constants). Long paragraph uses .performance-line rows + observer + CSS (same pattern as footer rows).

6. Features

Pinned #f-canvas with scroll-scrubbed model rotation and texture swaps coordinated with .box1.box5 opacity; videos preloaded via hidden <video> elements in useEffect (still static files, not an API).

7. Highlights

Masonry-style cards; GSAP reveals columns; individual cards / subtitle may use IntersectionObserver + classes in index.css.

8. Footer

Legal copy + links; no fetch. Rows use .performance-line + footer-reveal-ready / is-inview (avoid Tailwind’s bare content utility on wrappers—it maps to CSS content: and can break production layout).

GSAP registration (once at app level) — already in src/App.tsx:

import gsap from "gsap";
import { ScrollTrigger } from "gsap/all";

gsap.registerPlugin(ScrollTrigger);

Zustand store (excerpt)src/store/index.ts:

import { create } from "zustand";

const useMacbookStore = create<MacbookState>()((set) => ({
  color: "#2e2c2e",
  setColor: (color) => set({ color }),
  scale: 0.08,
  setScale: (scale) => set({ scale }),
  texture: "/videos/feature-1.mp4",
  setTexture: (texture) => set({ texture }),
  reset: () =>
    set({ color: "#2e2c2e", scale: 0.08, texture: "/videos/feature-1.mp4" }),
}));

Environment variables (.env)

You do not need any .env file to run, build, or deploy this project. There are no required API keys, database URLs, or third-party tokens in the source.

Optional (future): if you add a backend or analytics, follow Vite’s rule: only variables prefixed with VITE_ are exposed to browser code.

# .env.local (example only — not required today)
VITE_PUBLIC_APP_NAME=MacBook Pro Landing
const title =
  import.meta.env.VITE_PUBLIC_APP_NAME ?? "MacBook Pro Landing Page";

For production secrets, use your host’s environment variables UI (e.g. Vercel) and never commit real secrets to git.


How to run & npm scripts

Prerequisites: Node.js 20+ (LTS recommended) and npm.

npm install          # install dependencies
npm run dev          # http://localhost:5173 (Vite dev server + HMR)
ScriptWhat it does
npm run devStart Vite dev server.
npm run buildProduction build → dist/.
npm run previewServe dist/ locally to verify production output.
npm run lintESLint (flat config).
npm run type-checktsc -b full typecheck, no emit.

Build, preview & deploy

npm run build
npm run preview

Vercel: connect the Git repository or upload the dist/ folder as a static site. vercel.json contains a SPA rewrite so every path serves index.html (fixes 404 on refresh for client-only apps). Tune caching and headers in the Vercel dashboard if you add API routes elsewhere.


Reusing components in other projects

PieceHow to reuse
Section + CSSCopy the .tsx file and the matching #section rules from src/index.css, or extract styles to CSS modules.
Zustand storeMove store/index.ts into a shared lib/ package; keep types next to the store.
R3F canvasWrap <Canvas> in Suspense; preload GLBs with useGLTF.preload from drei in the same module where you use the model.
ScrollTriggerKeep registerPlugin once per app; on route changes (if you add a router), ScrollTrigger.getAll().forEach(t => t.kill()) or refresh.
Constants → CMSReplace static arrays with fetch + loading states when you outgrow hardcoded copy.

Minimal “drop-in” Hero pattern (conceptual)

  1. Copy Hero.tsx + hero block from index.css.
  2. Ensure #root / critical CSS strategy matches your CTA anti-flash needs.
  3. Replace video and image paths under public/.

Styling & motion docs

  • docs/UI_STYLING_GUIDE.md — Tailwind-oriented design notes; includes a GSAP / scroll / viewport reveal section for this repo.
  • docs/PARALLAX_SCROLL_REVEAL_SYSTEM.md — Scroll-linked and reveal patterns (if present in your clone).
  • docs/VERCEL_PRODUCTION_GUARDRAILS.md — Deployment notes.

Linting & type-checking

npm run lint
npm run type-check

ESLint uses eslint.config.js (flat config) with TypeScript ESLint, React Hooks, and React Refresh rules suited for Vite.


Further reading


Conclusion

macbook-ui is a compact open-source teaching sandbox: one continuous scroll story, two high-impact layers (GSAP motion + Three.js/R3F), and minimal global state. It deliberately avoids backends and env complexity so you can focus on layout → motion → WebGL → polish, then add APIs, auth, or a meta-framework when your roadmap requires them.


License

This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.

Happy Coding! 🎉

This is an open-source project - feel free to use, enhance, and extend this project further!

If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.

Enjoy building and learning! 🚀

Thank you! 😊


metadata.json
TypeScripteducational-projectfrontend-developmentgaspgasp-tutorialShowcaseslearning-projectmacbook-marketingmacbook-showcasereactreact-learningReact Three Fiberreact-tutorialscroll-driven-animationstailwindcssThree.jstypescriptviewport-driven-cssviteWebGLzustand

[INFO] 4 topics link to curated motion topic pages.