Skip to content

Instantly share code, notes, and snippets.

@al3rez
Created April 26, 2026 14:46
Show Gist options
  • Select an option

  • Save al3rez/17d96f88e93f85a392bec354ce1f0690 to your computer and use it in GitHub Desktop.

Select an option

Save al3rez/17d96f88e93f85a392bec354ce1f0690 to your computer and use it in GitHub Desktop.

Build a designer-portfolio landing page in React + Tailwind v4 + Framer Motion + @react-three/drei.

HERO

  • Full-bleed hero (no rounded box, edge-to-edge): wrapper is w-full with inline style background: radial-gradient(125% 125% at 50% 90%, #fff 40%, #63e 100%).

  • Inside the hero, render floating 3D clouds with @react-three/drei's inside . Use 3 seeds at low/wide positions, slow speed (0.16–0.20), volume ~2.2–2.6, segments={40}, color #fff, opacity 1, fade 0. Canvas camera position [0,0,10], fov 45, alpha true, antialias true, pointer-events none.

  • The bottom of the gradient must FADE into the white page below, no hard cut. Use an absolutely-positioned overlay: pointer-events-none absolute inset-x-0 bottom-0 h-40 z-[1] bg-gradient-to-b from-transparent to-white.

  • Hero text content sits at z-30 with relative positioning so it stays above the fade.

  • Hero copy: huge headline (text-4xl md:text-6xl lg:text-[72px] font-semibold tracking-tight), short subhead, and two CTAs side-by-side.

NAVBAR

  • Floating dark pill nav, fixed top-8 left-1/2 -translate-x-1/2 z-50.

  • Inner pill: flex items-center gap-6 bg-stone-900 text-white rounded-full px-6 py-3 shadow-2xl shadow-stone-900/20.

  • Contents: logo on left, then a hidden-on-mobile group of links (Work, Why us, Plans, Testimonials) anchoring to #work, #why, #plans, #testimonials. All links text-white with hover:opacity-80 transition-opacity.

  • NO Book-a-call button visible by default.

SCROLL-TRIGGERED BOOK-A-CALL

  • useState scrolled, useEffect with passive scroll listener that sets scrolled = window.scrollY > 200.

  • After the links, render a motion.div wrapper:

  • className: flex items-center gap-3 overflow-hidden pr-1

  • initial={false}

  • animate={{ width: scrolled ? "auto" : 0, marginLeft: scrolled ? 0 : -24, opacity: scrolled ? 1 : 0 }}

  • transition={{ duration: 0.4, ease: [0.4, 0.36, 0, 1] }}

  • style={{ willChange: "width, margin, opacity" }}

  • Inside the wrapper: a thin vertical divider and the Book-a-call using btnSecondary with !px-4 !py-1.5 !text-sm whitespace-nowrap shrink-0.

  • Effect: as the user scrolls, the navbar smoothly opens, the divider appears, and the button slides out from behind it. Reverse on scroll up.

REUSABLE BUTTON PRIMITIVES (define once, use everywhere)

  • btnPrimary (black pill, shimmer):

"group relative isolate inline-flex items-center justify-center overflow-hidden font-semibold transition duration-300 ease-[cubic-bezier(0.4,0.36,0,1)] before:duration-300 before:ease-[cubic-bezier(0.4,0.36,0,1)] before:transition-opacity rounded-full shadow-[0_1px_theme(colors.white/0.07)_inset,0_1px_3px_theme(colors.gray.900/0.2)] before:pointer-events-none before:absolute before:inset-0 before:-z-10 before:rounded-full before:bg-gradient-to-b before:from-white/20 before:opacity-50 hover:before:opacity-100 after:pointer-events-none after:absolute after:inset-0 after:-z-10 after:rounded-full after:bg-gradient-to-b after:from-white/10 after:from-[46%] after:to-[54%] after:mix-blend-overlay text-sm px-6 py-3.5 ring-1 bg-black text-white ring-black"

  • btnSecondary (white pill, inset shadow):

"inline-flex items-center justify-center overflow-clip rounded-full px-6 py-3.5 font-semibold text-sm text-stone-950 shadow-[0_2px_3px_-1px_theme(colors.black/0.08),0_0_0_0.5px_theme(colors.gray.950/0.18),0_1px_0_0_theme(colors.white/0.10)_inset] [background:linear-gradient(180deg,rgba(19,19,22,0)_45%,rgba(19,19,22,0.03)_55%),#fff] hover:brightness-[0.97] transition-all"

  • Use these for ALL CTAs: hero (Book a call = primary, View work = secondary), plans (highlighted plan = secondary, other = primary), bottom CTA (Book a call = primary, DM me on X = secondary), navbar Book-a-call (secondary, smaller).

SIGNATURE PILL

  • Below the hero CTAs, a small "Working with startups and teams since 2019" pill:

className="relative z-30 text-black/60 text-sm mt-16 inline-block border border-white rounded-full px-4 py-2 bg-white/70 backdrop-blur-sm"

  • z-30 keeps it above the fade overlay; backdrop-blur-sm keeps it readable.

MARQUEE

  • Below the hero gradient, an infinite-scrolling row of client name pills.

  • motion.div animate={{ x: ["0%", "-50%"] }} transition={{ duration: 18, ease: "linear", repeat: Infinity }} className="flex gap-4 whitespace-nowrap".

  • Each pill: text-stone-400 text-sm font-medium px-4 py-2 rounded-full border border-stone-200.

  • Wrapper: max-w-[1100px] mx-auto mt-2 overflow-hidden.

SPACING

  • Hero inner content: pt-40 md:pt-52 pb-10 md:pb-12 px-6 (so headline clears the floating nav).

  • Gradient bottom margin mb-4, marquee mt-2 (tight gap, no dead space).

STACK

  • React 19, Tailwind v4 (@tailwindcss/vite), Framer Motion (import { motion } from "motion/react"), @react-three/fiber, @react-three/drei, lucide-react for icons. Use clsx + tailwind-merge via a cn() helper.

Drei clouds docs: https://drei.docs.pmnd.rs/abstractions/clouds

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment