← GalleryDownload source code
- Category
- Portfolios
- Tier
- Free
- Prompt
- 3095 words
- Includes
- Prompt + source code
Portfolios
3D Creator Portfolio — Magnetic Portrait & Sticky Cards
framer-motionreacttailwindtypescriptvite
The prompt
# Jack — 3D Creator Portfolio Landing Page
## Overview
Build a single-page, dark-themed portfolio landing page for a 3D creator named "Jack." The page is a vertical scroll experience composed of five sections (hero, scroll-driven image marquee, about, services, and projects) with scroll-reactive animations, a mouse-following magnetic portrait, scroll-revealed text, and sticky-stacking project cards.
## Tech Stack
- **Framework:** React 18 (`react` / `react-dom` `^18.3.1`) with TypeScript (`^5.5.3`).
- **Build tool:** Vite (`^5.4.2`) with `@vitejs/plugin-react` (`^4.3.1`).
- **Styling:** Tailwind CSS (`^3.4.1`) with `postcss` (`^8.4.35`) and `autoprefixer` (`^10.4.18`).
- **Animation:** Framer Motion (`framer-motion` `^12.38.0`) — `motion`, `useScroll`, `useTransform`, `motion.create()`.
- **Icons:** Lucide React (`lucide-react` `^0.344.0`).
- **Font:** Kanit from Google Fonts, weights 300–900.
- **Notable techniques:** scroll-position-driven marquee, mouse-magnetic hover, character-by-character scroll-reveal text, sticky card stacking with scroll-linked scale, fluid `clamp()` typography.
## Global Setup
### Document (`index.html`)
- **Page title:** `Jack — 3D Creator`.
- **Fonts:** load Kanit from Google Fonts (weights 300–900).
- **Root + entry:** `<div id="root"></div>` and `<script type="module" src="/src/main.tsx"></script>`.
### Tailwind config (`tailwind.config.js`)
- `content: ['./index.html', './src/**/*.{ts,tsx}']`.
- Extend `fontFamily` with `kanit: ['Kanit', 'sans-serif']` (exposes the `font-kanit` utility).
- No plugins.
### PostCSS config (`postcss.config.js`)
- Plugins: `tailwindcss` and `autoprefixer`.
### Global styles (`src/index.css`)
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
html,
body,
#root {
background-color: #0c0c0c;
}
body {
font-family: 'Kanit', sans-serif;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
.hero-heading {
background: linear-gradient(180deg, #646973 0%, #bbccd7 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
}
```
The `.hero-heading` class produces the silver gradient text used by all major headings.
### App entry (`src/main.tsx`)
```tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
```
### Root layout (`src/App.tsx`)
A `<main>` wrapper with `className="min-h-screen bg-[#0C0C0C] font-kanit"` and inline `style={{ overflowX: 'clip' }}`. Renders the sections in this order:
1. `HeroSection`
2. `MarqueeSection`
3. `AboutSection`
4. `ServicesSection`
5. `ProjectsSection`
## Assets
All media is vendored locally and served from the `public/assets/` directory (referenced at runtime as `/assets/...`). The list below maps each local path to its original external source URL.
**Portrait**
- `/assets/Rectangle_40443.81459862.png` — original: `https://shrug-person-78902957.figma.site/_components/v2/d24c01ad3a56fc65e942a1f501eb73db42d7cf9a/Rectangle_40443.81459862.png`
**Marquee GIFs (21, originals from motionsites.ai)**
| Local path | Original URL |
| --- | --- |
| `/assets/hero-space-voyage-preview-eECLH3Yc.gif` | `https://motionsites.ai/assets/hero-space-voyage-preview-eECLH3Yc.gif` |
| `/assets/hero-codenest-preview-Cgppc2qV.gif` | `https://motionsites.ai/assets/hero-codenest-preview-Cgppc2qV.gif` |
| `/assets/hero-vex-ventures-preview-BczMFIiw.gif` | `https://motionsites.ai/assets/hero-vex-ventures-preview-BczMFIiw.gif` |
| `/assets/hero-stellar-ai-v2-preview-DjvxjG3C.gif` | `https://motionsites.ai/assets/hero-stellar-ai-v2-preview-DjvxjG3C.gif` |
| `/assets/hero-asme-preview-B_nGDnTP.gif` | `https://motionsites.ai/assets/hero-asme-preview-B_nGDnTP.gif` |
| `/assets/hero-transform-data-preview-Cx5OU29N.gif` | `https://motionsites.ai/assets/hero-transform-data-preview-Cx5OU29N.gif` |
| `/assets/hero-vitara-preview-Cjz2QYyU.gif` | `https://motionsites.ai/assets/hero-vitara-preview-Cjz2QYyU.gif` |
| `/assets/hero-terra-preview-BFjrCr7T.gif` | `https://motionsites.ai/assets/hero-terra-preview-BFjrCr7T.gif` |
| `/assets/hero-skyelite-preview-DHaZIgUv.gif` | `https://motionsites.ai/assets/hero-skyelite-preview-DHaZIgUv.gif` |
| `/assets/hero-aethera-preview-DknSlcTa.gif` | `https://motionsites.ai/assets/hero-aethera-preview-DknSlcTa.gif` |
| `/assets/hero-designpro-preview-D8c5_een.gif` | `https://motionsites.ai/assets/hero-designpro-preview-D8c5_een.gif` |
| `/assets/hero-stellar-ai-preview-D3HL6bw1.gif` | `https://motionsites.ai/assets/hero-stellar-ai-preview-D3HL6bw1.gif` |
| `/assets/hero-xportfolio-preview-D4A8maiC.gif` | `https://motionsites.ai/assets/hero-xportfolio-preview-D4A8maiC.gif` |
| `/assets/hero-orbit-web3-preview-BXt4OttD.gif` | `https://motionsites.ai/assets/hero-orbit-web3-preview-BXt4OttD.gif` |
| `/assets/hero-nexora-preview-cx5HmUgo.gif` | `https://motionsites.ai/assets/hero-nexora-preview-cx5HmUgo.gif` |
| `/assets/hero-evr-ventures-preview-DZxeVFEX.gif` | `https://motionsites.ai/assets/hero-evr-ventures-preview-DZxeVFEX.gif` |
| `/assets/hero-planet-orbit-preview-DWAP8Z1P.gif` | `https://motionsites.ai/assets/hero-planet-orbit-preview-DWAP8Z1P.gif` |
| `/assets/hero-new-era-preview-CocuDUm9.gif` | `https://motionsites.ai/assets/hero-new-era-preview-CocuDUm9.gif` |
| `/assets/hero-wealth-preview-B70idl_u.gif` | `https://motionsites.ai/assets/hero-wealth-preview-B70idl_u.gif` |
| `/assets/hero-luminex-preview-CxOP7ce6.gif` | `https://motionsites.ai/assets/hero-luminex-preview-CxOP7ce6.gif` |
| `/assets/hero-celestia-preview-0yO3jXO8.gif` | `https://motionsites.ai/assets/hero-celestia-preview-0yO3jXO8.gif` |
**About decorative 3D images (originals from figma.site)**
- `/assets/moon_icon.11395d36.png` — original: `https://shrug-person-78902957.figma.site/_components/v2/ebb2b8f25d8e24d5f0a5ca8af4c950de81aa2fd7/moon_icon.11395d36.png`
- `/assets/p59_1.4659672e.png` — original: `https://shrug-person-78902957.figma.site/_components/v2/ebb2b8f25d8e24d5f0a5ca8af4c950de81aa2fd7/p59_1.4659672e.png`
- `/assets/lego_icon-1.703bb594.png` — original: `https://shrug-person-78902957.figma.site/_components/v2/ebb2b8f25d8e24d5f0a5ca8af4c950de81aa2fd7/lego_icon-1.703bb594.png`
- `/assets/Group_134-1.2e04f3ce.png` — original: `https://shrug-person-78902957.figma.site/_components/v2/ebb2b8f25d8e24d5f0a5ca8af4c950de81aa2fd7/Group_134-1.2e04f3ce.png`
**Project images (9 `.webp`)** — originals served via the `images.higgs.ai` proxy pointing at the `d8j0ntlcm91z4.cloudfront.net` bucket; vendored as `.webp` and listed per-project (with their full original proxy source URLs) in the [Projects Section](#5-projects-section).
## Color Palette
- **Background (dark):** `#0C0C0C` — html, body, `#root`, main wrapper, hero, marquee, projects, and project cards.
- **Light text / accents:** `#D7E2EA`.
- **Heading gradient:** `linear-gradient(180deg, #646973 0%, #bbccd7 100%)`.
- **Services background (light):** `white` (`#FFFFFF`).
- **Contact button gradient:** `linear-gradient(123deg, #18011F 7%, #B600A8 37%, #7621B0 72%, #BE4C00 100%)`.
- **Service item border:** `rgba(12,12,12,0.15)`.
## Reusable Components
### `FadeIn` (`src/components/FadeIn.tsx`)
A Framer Motion wrapper that fades and slides content in when it scrolls into view.
- Props: `children`, `as` (one of `'div' | 'nav' | 'span' | 'section' | 'header' | 'footer'`, default `'div'`), `className`, `style`, `delay` (default `0`), `duration` (default `0.7`), `x` (default `0`), `y` (default `30`).
- Uses `motion.create(as)` (memoized) to support dynamic element types.
- `initial={{ opacity: 0, x, y }}`, `whileInView={{ opacity: 1, x: 0, y: 0 }}`.
- `viewport={{ once: true, margin: '50px', amount: 0 }}`.
- `transition={{ delay, duration, ease: [0.25, 0.1, 0.25, 1] }}`.
```tsx
import { useMemo } from 'react'
import type { CSSProperties, ReactNode } from 'react'
import { motion } from 'framer-motion'
type FadeInTag = 'div' | 'nav' | 'span' | 'section' | 'header' | 'footer'
interface FadeInProps {
children: ReactNode
as?: FadeInTag
className?: string
style?: CSSProperties
delay?: number
duration?: number
x?: number
y?: number
}
export default function FadeIn({
children,
as = 'div',
className,
style,
delay = 0,
duration = 0.7,
x = 0,
y = 30,
}: FadeInProps) {
const MotionTag = useMemo(() => motion.create(as), [as])
return (
<MotionTag
className={className}
style={style}
initial={{ opacity: 0, x, y }}
whileInView={{ opacity: 1, x: 0, y: 0 }}
viewport={{ once: true, margin: '50px', amount: 0 }}
transition={{ delay, duration, ease: [0.25, 0.1, 0.25, 1] }}
>
{children}
</MotionTag>
)
}
```
### `Magnet` (`src/components/Magnet.tsx`)
A mouse-following magnetic hover effect: the wrapped content drifts toward the cursor (offset divided by `strength`) once the cursor comes within `padding` px of the element's edge, then eases back when it leaves.
- Props: `children`, `padding` (default `100`), `strength` (default `2`), `activeTransition` (default `'transform 0.3s ease-out'`), `inactiveTransition` (default `'transform 0.6s ease-in-out'`), `className`.
- Tracks the mouse via a `window` `mousemove` listener; computes element center from `getBoundingClientRect()`.
- Active when `distX < width / 2 + padding && distY < height / 2 + padding`; then sets `position` to `(clientX - centerX) / strength`, `(clientY - centerY) / strength`. Otherwise resets to `{ x: 0, y: 0 }`.
- Applies `transform: translate3d(${x}px, ${y}px, 0)` with `transition` toggled between active/inactive and `willChange: 'transform'`.
```tsx
import { useEffect, useRef, useState } from 'react'
import type { ReactNode } from 'react'
interface MagnetProps {
children: ReactNode
padding?: number
strength?: number
activeTransition?: string
inactiveTransition?: string
className?: string
}
export default function Magnet({
children,
padding = 100,
strength = 2,
activeTransition = 'transform 0.3s ease-out',
inactiveTransition = 'transform 0.6s ease-in-out',
className,
}: MagnetProps) {
const wrapperRef = useRef<HTMLDivElement>(null)
const [isActive, setIsActive] = useState(false)
const [position, setPosition] = useState({ x: 0, y: 0 })
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
const wrapper = wrapperRef.current
if (!wrapper) return
const { left, top, width, height } = wrapper.getBoundingClientRect()
const centerX = left + width / 2
const centerY = top + height / 2
const distX = Math.abs(centerX - e.clientX)
const distY = Math.abs(centerY - e.clientY)
if (distX < width / 2 + padding && distY < height / 2 + padding) {
setIsActive(true)
setPosition({
x: (e.clientX - centerX) / strength,
y: (e.clientY - centerY) / strength,
})
} else {
setIsActive(false)
setPosition({ x: 0, y: 0 })
}
}
window.addEventListener('mousemove', handleMouseMove)
return () => window.removeEventListener('mousemove', handleMouseMove)
}, [padding, strength])
return (
<div ref={wrapperRef} className={className} style={{ position: 'relative' }}>
<div
style={{
transform: `translate3d(${position.x}px, ${position.y}px, 0)`,
transition: isActive ? activeTransition : inactiveTransition,
willChange: 'transform',
}}
>
{children}
</div>
</div>
)
}
```
### `AnimatedText` (`src/components/AnimatedText.tsx`)
A character-by-character scroll-reveal paragraph: each character fades from opacity `0.2` to `1` as the paragraph moves through the viewport. Characters are grouped per word (`inline-block`) so line wrapping only happens at spaces.
- Props: `text`, `className?`, `style?`.
- Uses `useScroll({ target: ref, offset: ['start 0.8', 'end 0.2'] })` on the `<p>` element.
- For each character, an inner `Char` maps `useTransform(progress, range, [0.2, 1])` to opacity, where `range = [globalIndex / totalChars, Math.min((globalIndex + 1) / totalChars, 1)]`.
- Each `Char` renders an invisible placeholder (`<span className="invisible">`) plus an absolutely positioned animated `motion.span` (`absolute left-0 top-0`).
```tsx
import { useRef } from 'react'
import type { CSSProperties } from 'react'
import { motion, useScroll, useTransform } from 'framer-motion'
import type { MotionValue } from 'framer-motion'
interface AnimatedTextProps {
text: string
className?: string
style?: CSSProperties
}
interface CharProps {
char: string
progress: MotionValue<number>
range: [number, number]
}
function Char({ char, progress, range }: CharProps) {
const opacity = useTransform(progress, range, [0.2, 1])
return (
<span className="relative inline-block">
<span className="invisible">{char}</span>
<motion.span className="absolute left-0 top-0" style={{ opacity }}>
{char}
</motion.span>
</span>
)
}
export default function AnimatedText({ text, className, style }: AnimatedTextProps) {
const ref = useRef<HTMLParagraphElement>(null)
const { scrollYProgress } = useScroll({
target: ref,
offset: ['start 0.8', 'end 0.2'],
})
const words = text.split(' ')
const totalChars = text.length
let charCursor = 0
return (
<p ref={ref} className={className} style={style}>
{words.map((word, wordIndex) => {
const wordStart = charCursor
charCursor += word.length + 1
return (
<span key={wordIndex}>
<span className="inline-block">
{word.split('').map((char, charIndex) => {
const globalIndex = wordStart + charIndex
return (
<Char
key={charIndex}
char={char}
progress={scrollYProgress}
range={[
globalIndex / totalChars,
Math.min((globalIndex + 1) / totalChars, 1),
]}
/>
)
})}
</span>
{wordIndex < words.length - 1 ? ' ' : null}
</span>
)
})}
</p>
)
}
```
### `ContactButton` (`src/components/ContactButton.tsx`)
A rounded-full pill button with the gradient background. Label: **Contact Me**.
- Classes: `inline-block rounded-full px-8 py-3 text-center text-xs font-medium uppercase tracking-widest text-white transition-[filter,transform] duration-200 hover:scale-[1.03] hover:brightness-110 active:scale-[0.98] sm:px-10 sm:py-3.5 sm:text-sm md:px-12 md:py-4 md:text-base`.
- Inline style:
- `background: 'linear-gradient(123deg, #18011F 7%, #B600A8 37%, #7621B0 72%, #BE4C00 100%)'`
- `boxShadow: '0px 4px 4px rgba(181, 1, 167, 0.25), 4px 4px 12px #7721B1 inset'`
- `outline: '2px solid #FFFFFF'`, `outlineOffset: '-3px'`
### `LiveProjectButton` (`src/components/LiveProjectButton.tsx`)
A ghost/outline pill `<button type="button">`. Label: **Live Project**.
- Classes: `rounded-full border-2 border-[#D7E2EA] px-8 py-3 text-sm font-medium uppercase tracking-widest text-[#D7E2EA] transition-colors duration-200 hover:bg-[#D7E2EA]/10 sm:px-10 sm:py-3.5 sm:text-base`.
## Sections
### 1. Hero Section
File: `src/sections/HeroSection.tsx`. A `<section>` with `className="relative flex h-screen flex-col"` and inline `style={{ overflowX: 'clip' }}` — full viewport height, flex column.
**Navbar** — a `FadeIn as="nav"` (`delay={0}`, `y={-20}`) with `className="flex items-center justify-between px-6 pt-6 md:px-10 md:pt-8"`. Four links, each styled `text-sm font-medium uppercase tracking-wider text-[#D7E2EA] transition-opacity duration-200 hover:opacity-70 md:text-lg lg:text-[1.4rem]`:
| Label | `href` |
| --- | --- |
| About | `#about` |
| Price | `#price` |
| Projects | `#projects` |
| Contact | _(no href)_ |
**Hero heading** — wrapped in a `overflow-hidden` container, then a `FadeIn` (`delay={0.15}`, `y={40}`) holding an `<h1>` with the `hero-heading` gradient text. Copy: `Hi, i'm jack` (lowercase "i", curly apostrophe via `'`).
- Classes: `hero-heading mt-6 w-full whitespace-nowrap text-center text-[14vw] font-black uppercase leading-none tracking-tight sm:mt-4 sm:text-[15vw] md:-mt-5 md:text-[16vw] lg:text-[17.5vw]`.
**Hero portrait** — centered, in an absolutely positioned wrapper: `absolute left-1/2 top-1/2 z-10 -translate-x-1/2 -translate-y-1/2 sm:bottom-0 sm:top-auto sm:translate-y-0` (vertically centered on mobile; pinned to the bottom from `sm` up). Inside: `FadeIn` (`delay={0.6}`, `y={30}`) wrapping a `Magnet` (`padding={150}`, `strength={3}`, `activeTransition="transform 0.3s ease-out"`, `inactiveTransition="transform 0.6s ease-in-out"`) wrapping the image.
- Image `src`: `/assets/Rectangle_40443.81459862.png`, `alt="Portrait of Jack, 3D creator"`, `draggable={false}`.
- Image classes: `pointer-events-none w-[280px] select-none sm:w-[360px] md:w-[440px] lg:w-[520px]`.
**Bottom bar** — a flex container pushed to the bottom: `mt-auto flex items-end justify-between px-6 pb-7 sm:pb-8 md:px-10 md:pb-10`.
- **Left:** `FadeIn` (`delay={0.35}`, `y={20}`) wrapping a `<p>`. Copy: `a 3d creator driven by crafting striking and unforgettable projects`.
- Classes: `max-w-[160px] font-light uppercase leading-snug tracking-wide text-[#D7E2EA] sm:max-w-[220px] md:max-w-[260px]`; inline `style={{ fontSize: 'clamp(0.75rem, 1.4vw, 1.5rem)' }}`.
- **Right:** `FadeIn` (`delay={0.5}`, `y={20}`, `className="relative z-20"`) wrapping `ContactButton`.
**Hero fade-in timing summary:** navbar `delay 0, y -20`; heading `delay 0.15, y 40`; left text `delay 0.35, y 20`; contact button `delay 0.5, y 20`; portrait `delay 0.6, y 30`.
### 2. Marquee Section
File: `src/sections/MarqueeSection.tsx`. Two rows of images that scroll horizontally based on page scroll position.
- `<section>` classes: `flex flex-col gap-3 bg-[#0C0C0C] pb-10 pt-24 sm:pt-32 md:pt-40`.
- The 21 marquee GIFs (see [Assets](#assets)) are split: **Row 1** = first 11 images (`slice(0, 11)`); **Row 2** = remaining 10 images (`slice(11)`).
- Each row is **tripled** (`[...images, ...images, ...images]`) for seamless looping.
- Row container classes: `flex w-max gap-3` with `style={{ transform: rowTransform(initialOffset), willChange: 'transform' }}`.
- Each image tile: `h-[270px] w-[420px] shrink-0 rounded-2xl object-cover`, `loading="lazy"`, empty `alt=""`.
**Scroll behavior.** Rows are pre-shifted left by one copy via `translateX(calc(-33.3333% + ${offsetPx}px))` so the scroll-driven offset can move them in either direction without exposing an edge gap.
```ts
const rowTransform = (offsetPx: number) => `translateX(calc(-33.3333% + ${offsetPx}px))`
```
On `scroll` (passive listener) and `resize`, compute:
```ts
const offset = (window.scrollY - section.offsetTop + window.innerHeight) * 0.3
rowOne.style.transform = rowTransform(offset - 200) // moves right on scroll
rowTwo.style.transform = rowTransform(-(offset - 200)) // moves left on scroll
```
- Initial offsets: Row 1 `initialOffset={-200}`, Row 2 `initialOffset={200}`.
- `update()` runs once on mount; listeners are cleaned up on unmount.
### 3. About Section
File: `src/sections/AboutSection.tsx`. A full-height centered section: `<section id="about" className="relative flex min-h-screen flex-col items-center justify-center px-5 py-20 sm:px-8 md:px-10">`.
**Four decorative 3D images** positioned absolutely in the corners, each wrapped `pointer-events-none absolute ${position}` then a `FadeIn` (`y={0}`, `duration={0.9}`) wrapping an `<img>` (`alt=""`, `loading="lazy"`, `draggable={false}`) with the listed width classes:
| Image (`src`) | Position classes | Width classes | `delay` | `x` |
| --- | --- | --- | --- | --- |
| `/assets/moon_icon.11395d36.png` (top-left moon) | `top-[4%] left-[1%] sm:left-[2%] md:left-[4%]` | `w-[120px] sm:w-[160px] md:w-[210px]` | `0.1` | `-80` |
| `/assets/p59_1.4659672e.png` (bottom-left 3D object) | `bottom-[8%] left-[3%] sm:left-[6%] md:left-[10%]` | `w-[100px] sm:w-[140px] md:w-[180px]` | `0.25` | `-80` |
| `/assets/lego_icon-1.703bb594.png` (top-right lego) | `top-[4%] right-[1%] sm:right-[2%] md:right-[4%]` | `w-[120px] sm:w-[160px] md:w-[210px]` | `0.15` | `80` |
| `/assets/Group_134-1.2e04f3ce.png` (bottom-right 3D group) | `bottom-[8%] right-[3%] sm:right-[6%] md:right-[10%]` | `w-[130px] sm:w-[170px] md:w-[220px]` | `0.3` | `80` |
**Content stack** — outer wrapper `flex flex-col items-center gap-16 sm:gap-20 md:gap-24`. Inside, an inner stack `flex flex-col items-center gap-10 sm:gap-14 md:gap-16` containing:
- **Heading:** `FadeIn` (`delay={0}`, `y={40}`) wrapping an `<h2>`. Copy: `About me`.
- Classes: `hero-heading text-center font-black uppercase leading-none tracking-tight`; inline `style={{ fontSize: 'clamp(3rem, 12vw, 160px)' }}`.
- **Animated paragraph:** `AnimatedText` with the text below.
- Classes: `max-w-[560px] text-center font-medium leading-relaxed text-[#D7E2EA]`; inline `style={{ fontSize: 'clamp(1rem, 2vw, 1.35rem)' }}`.
- Text (verbatim): `With more than five years of experience in design, i focus on branding, web design, and user experience, i truly enjoy working with businesses that aim to stand out and present their best image. Let's build something incredible together!`
- Note: the apostrophe in "Let's" is a curly apostrophe (`’`).
Below the inner stack: a `FadeIn` (`delay={0.15}`, `y={20}`) wrapping `ContactButton`.
### 4. Services Section
File: `src/sections/ServicesSection.tsx`. A white-background panel with rounded top corners: `<section id="price" className="relative rounded-t-[40px] bg-white px-5 py-20 sm:rounded-t-[50px] sm:px-8 sm:py-24 md:rounded-t-[60px] md:px-10 md:py-32">`.
**Heading** — `FadeIn` (`delay={0}`, `y={40}`) wrapping an `<h2>`. Copy: `Services`.
- Classes: `mb-16 text-center font-black uppercase leading-none tracking-tight text-[#0C0C0C] sm:mb-20 md:mb-28`; inline `style={{ fontSize: 'clamp(3rem, 12vw, 160px)' }}`.
**Service list** — centered container `mx-auto max-w-5xl`. Five items, each a `FadeIn` with `delay={i * 0.1}` (staggered). Every item except the last gets `className="border-b border-[rgba(12,12,12,0.15)]"`.
Each item is a row `flex items-start gap-6 py-8 sm:gap-10 sm:py-10 md:gap-14 md:py-12`:
- **Number** — `<span className="font-black leading-none text-[#0C0C0C]">` with inline `style={{ fontSize: 'clamp(3rem, 10vw, 140px)' }}`.
- **Name + description** stacked — wrapper `flex flex-col gap-2 pt-1 sm:gap-3 sm:pt-2`:
- **Name** `<h3 className="font-medium uppercase text-[#0C0C0C]">`, inline `style={{ fontSize: 'clamp(1rem, 2.2vw, 2.1rem)' }}`.
- **Description** `<p className="max-w-2xl font-light leading-relaxed text-[#0C0C0C]">`, inline `style={{ fontSize: 'clamp(0.85rem, 1.6vw, 1.25rem)', opacity: 0.6 }}`.
Service data (verbatim):
1. **01 — 3D Modeling:** `Creation of detailed objects, characters, or environments tailored to specific client needs, ideal for games, products, and visualizations.`
2. **02 — Rendering:** `High-quality, photorealistic renders that showcase designs with custom lighting, textures, and materials to bring concepts to life.`
3. **03 — Motion Design:** `Dynamic animations and motion graphics that add energy and storytelling to brands, products, and digital experiences.`
4. **04 — Branding:** `Crafting cohesive visual identities — from logos to full brand systems — that communicate a clear and memorable presence.`
5. **05 — Web Design:** `Designing clean, modern, and conversion-focused websites with attention to layout, typography, and user experience.`
### 5. Projects Section
File: `src/sections/ProjectsSection.tsx`. A dark panel pulled up over the services section: `<section id="projects" className="relative z-10 -mt-10 rounded-t-[40px] bg-[#0C0C0C] px-5 pb-16 pt-20 sm:-mt-12 sm:rounded-t-[50px] sm:px-8 md:-mt-14 md:rounded-t-[60px] md:px-10">`.
**Heading** — `FadeIn` (`delay={0}`, `y={40}`) wrapping an `<h2>` with the `hero-heading` gradient. Copy: `Project` (singular).
- Classes: `hero-heading mb-16 text-center font-black uppercase leading-none tracking-tight sm:mb-20 md:mb-28`; inline `style={{ fontSize: 'clamp(3rem, 12vw, 160px)' }}`.
**Card stack** — container `<div ref={containerRef} className="mx-auto max-w-7xl">`. Scroll progress is tracked with `useScroll({ target: containerRef, offset: ['start start', 'end end'] })`. Three sticky-stacking project cards scale down as you scroll past them.
**`ProjectCard`** behavior:
- Outer wrapper `h-[85vh]`; inner `sticky top-24 md:top-32`.
- A `motion.div` with classes `${CARD_RADIUS} border-2 border-[#D7E2EA] bg-[#0C0C0C] p-4 sm:p-6 md:p-8`, where `CARD_RADIUS = 'rounded-[40px] sm:rounded-[50px] md:rounded-[60px]'`.
- Inline style: `scale` (a `MotionValue`), `top: \`${index * 28}px\``, `position: 'relative'`, `transformOrigin: 'top center'`.
- Scale math:
```ts
const targetScale = 1 - (total - 1 - index) * 0.03
const scale = useTransform(progress, [index / total, 1], [1, targetScale])
```
**Card top row** — `flex flex-wrap items-center justify-between gap-x-6 gap-y-4 px-2 pb-4 sm:px-4 sm:pb-6 md:px-6 md:pb-8`. Left group `flex items-center gap-4 sm:gap-6 md:gap-8`:
- **Number** `<span className="hero-heading font-black leading-none">`, inline `style={{ fontSize: 'clamp(3rem, 10vw, 140px)' }}`.
- A `flex flex-col gap-1` stack:
- **Category** `<span className="font-light uppercase tracking-widest text-[#D7E2EA]">`, inline `style={{ fontSize: 'clamp(0.7rem, 1.2vw, 1rem)', opacity: 0.6 }}`.
- **Name** `<h3 className="font-medium uppercase leading-tight text-[#D7E2EA]">`, inline `style={{ fontSize: 'clamp(1.1rem, 2.4vw, 2.2rem)' }}`.
On the right: `LiveProjectButton`.
**Card bottom row** — two-column image grid `flex gap-3 sm:gap-4`:
- **Left column (40%)** `flex w-[40%] flex-col gap-3 sm:gap-4` with two stacked images, both `${CARD_RADIUS} w-full object-cover`:
- Top image (`col1[0]`), `alt="${project.name} — visual 1"`, inline `style={{ height: 'clamp(130px, 16vw, 230px)' }}`.
- Bottom image (`col1[1]`), `alt="${project.name} — visual 2"`, inline `style={{ height: 'clamp(160px, 22vw, 340px)' }}`.
- **Right column (60%)** `w-[60%]` with one tall image (`col2`), `alt="${project.name} — visual 3"`, classes `${CARD_RADIUS} h-full w-full object-cover`.
- All images `loading="lazy"`.
**Project data** (numbers, names, categories, and local `.webp` image paths mapped to their original `images.higgs.ai` proxy source URLs):
**Project 01 — Nextlevel Studio (Client)**
- col1 image 1: `/assets/hf_20260412_055344_5eff02e0-87a5-41ce-b64f-eb08da8f33db.webp` — original: `https://images.higgs.ai/?default=1&output=webp&url=https%3A%2F%2Fd8j0ntlcm91z4.cloudfront.net%2Fuser_38xzZboKViGWJOttwIXH07lWA1P%2Fhf_20260412_055344_5eff02e0-87a5-41ce-b64f-eb08da8f33db.png&w=1280&q=85`
- col1 image 2: `/assets/hf_20260412_055431_11d841fd-8b41-46a5-82e4-b04f2407a7d8.webp` — original: `https://images.higgs.ai/?default=1&output=webp&url=https%3A%2F%2Fd8j0ntlcm91z4.cloudfront.net%2Fuser_38xzZboKViGWJOttwIXH07lWA1P%2Fhf_20260412_055431_11d841fd-8b41-46a5-82e4-b04f2407a7d8.png&w=1280&q=85`
- col2 image: `/assets/hf_20260412_055451_e317bf2d-28d4-48cc-86b0-6f72f25b6327.webp` — original: `https://images.higgs.ai/?default=1&output=webp&url=https%3A%2F%2Fd8j0ntlcm91z4.cloudfront.net%2Fuser_38xzZboKViGWJOttwIXH07lWA1P%2Fhf_20260412_055451_e317bf2d-28d4-48cc-86b0-6f72f25b6327.png&w=1280&q=85`
**Project 02 — Aura Brand Identity (Personal)**
- col1 image 1: `/assets/hf_20260412_055654_911201c5-36d9-4bc6-bac7-331adfce159f.webp` — original: `https://images.higgs.ai/?default=1&output=webp&url=https%3A%2F%2Fd8j0ntlcm91z4.cloudfront.net%2Fuser_38xzZboKViGWJOttwIXH07lWA1P%2Fhf_20260412_055654_911201c5-36d9-4bc6-bac7-331adfce159f.png&w=1280&q=85`
- col1 image 2: `/assets/hf_20260412_055723_5ceda0b8-d9c2-4665-b2e3-83ba19ba76d1.webp` — original: `https://images.higgs.ai/?default=1&output=webp&url=https%3A%2F%2Fd8j0ntlcm91z4.cloudfront.net%2Fuser_38xzZboKViGWJOttwIXH07lWA1P%2Fhf_20260412_055723_5ceda0b8-d9c2-4665-b2e3-83ba19ba76d1.png&w=1280&q=85`
- col2 image: `/assets/hf_20260412_055753_adc5dcbd-a8e6-49c0-b43a-9b030d835cea.webp` — original: `https://images.higgs.ai/?default=1&output=webp&url=https%3A%2F%2Fd8j0ntlcm91z4.cloudfront.net%2Fuser_38xzZboKViGWJOttwIXH07lWA1P%2Fhf_20260412_055753_adc5dcbd-a8e6-49c0-b43a-9b030d835cea.png&w=1280&q=85`
**Project 03 — Solaris Digital (Client)**
- col1 image 1: `/assets/hf_20260412_055759_963cfb0b-4bd1-4b0f-9d0a-09bd6cf95b2f.webp` — original: `https://images.higgs.ai/?default=1&output=webp&url=https%3A%2F%2Fd8j0ntlcm91z4.cloudfront.net%2Fuser_38xzZboKViGWJOttwIXH07lWA1P%2Fhf_20260412_055759_963cfb0b-4bd1-4b0f-9d0a-09bd6cf95b2f.png&w=1280&q=85`
- col1 image 2: `/assets/hf_20260412_060108_438f781a-9846-4dcc-89ab-c4e6cb830f5b.webp` — original: `https://images.higgs.ai/?default=1&output=webp&url=https%3A%2F%2Fd8j0ntlcm91z4.cloudfront.net%2Fuser_38xzZboKViGWJOttwIXH07lWA1P%2Fhf_20260412_060108_438f781a-9846-4dcc-89ab-c4e6cb830f5b.png&w=1280&q=85`
- col2 image: `/assets/hf_20260412_055818_9d062121-ad7e-46b9-999a-1a6a692ef1ee.webp` — original: `https://images.higgs.ai/?default=1&output=webp&url=https%3A%2F%2Fd8j0ntlcm91z4.cloudfront.net%2Fuser_38xzZboKViGWJOttwIXH07lWA1P%2Fhf_20260412_055818_9d062121-ad7e-46b9-999a-1a6a692ef1ee.png&w=1280&q=85`
## Responsive Breakpoints
All sections use Tailwind's default breakpoints (`sm`: 640px, `md`: 768px, `lg`: 1024px) with a mobile-first approach. Heavy use of `clamp()` provides fluid typography, and the entire design scales gracefully from mobile to ultra-wide screens.
## Key Dependencies
- `react`, `react-dom` (`^18.3.1`)
- `framer-motion` (`^12.38.0`)
- `lucide-react` (`^0.344.0`)
- `tailwindcss` (`^3.4.1`)
- `vite` (`^5.4.2`), `typescript` (`^5.5.3`)
- Dev: `@types/react` (`^18.3.5`), `@types/react-dom` (`^18.3.0`), `@vitejs/plugin-react` (`^4.3.1`), `autoprefixer` (`^10.4.18`), `postcss` (`^8.4.35`)



