Craft Stunning Landing Pages with the Power of Next.js and Tailwind CSS
Navbar
<div className=' w-full p-4 flex items-center justify-center fixed top-0 z-50 '>
<div className='border bg-neutral-950/70 border-white/50 w-full lg:w-[800px] mx-5 md:mx-24 rounded-[40px] md:rounded-full backdrop-blur'>
fixed
: Positions the div fixed relative to the viewport, so it stays in place when scrolling
top-0
: Anchors the div to the very top of the screen
z-50
: Sets a high z-index to ensure the element appears above other content
backdrop-blur
: Applies a blur effect to the background behind the div, creating a frosted glass effect
<div className='flex justify-end pr-4'>
<svg xmlns="http://www.w3.org/2000/svg"
width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" strokeWidth="2"
strokeLinecap="round" strokeLinejoin="round"
className="feather feather-menu lg:hidden"
onClick={()=>isOpen(!open)}>
<line x1="3" y1="6" x2="21" y2="6"
className={twMerge("origin-left transition",open && 'rotate-45 -translate-y-1')}></line>
<line x1="3" y1="12" x2="21" y2="12"
className={twMerge("transition",open && 'hidden')}
></line>
<line x1="3" y1="18" x2="21" y2="18"
className={twMerge("origin-left transition",open && '-rotate-45 translate-y-1')}></line>
</svg>
</div>
</div>
<AnimatePresence>
{open &&
<motion.div
initial={{height:0}}
animate={{height:'auto'}}
exit={{height:0}}
className=' overflow-hidden '>
<div className='flex flex-col items-center py-4 gap-4'>
{Links.map((link)=>(
<p key={link.id}>{link.name}</p>
))}
<button className='rounded-full px-2 py-1 border border-lime-300/50 w-20'>Login</button>
<button className='rounded-full px-2 py-1 bg-lime-400 w-20 text-white'>Signup</button>
</div>
</motion.div>
}
</AnimatePresence>
To obtain the menu SVG logo use this link.
Logo animation (Framer-motion).
<div className=' flex overflow-hidden [mask-image:linear-gradient(to_right,transparent,black_10%,black_90%, transparent)]'>
<motion.div
animate={{
x:'-50%',
}}
transition={{
duration:20,
ease:'linear',
repeat:Infinity,
}}
className='gap-24 flex flex-none pr-24 items-center'>
{Array.from({length:2}).map((_,i)=>(
<Fragment key={i}>
{logos.map((logo)=>(
<Image src={logo.image} key={logo.id} alt=''
className='w-24'/>
)
)}
</Fragment>
))}
</motion.div>
</div>
<div className='flex overflow-hidden [mask-image:linear-gradient(to_right,transparent,black_10%,black_90%, transparent)]'>
flex
: Creates a flex containeroverflow-hidden
: Hides any content that exceeds the container's dimensions[mask-image:linear-gradient(...)]
: A custom mask that creates a fade effect on the sides of the containerThe gradient goes from transparent on the left, to black at 10% and 90%, then back to transparent
This creates a smooth fade-out effect on the horizontal edges
<motion.div
animate={{
x:'-50%',
}}
transition={{
duration:20,
ease:'linear',
repeat:Infinity,
}}
className='gap-24 flex flex-none pr-24 items-center'
>
animate={{ x:'-50%' }}
: Animates the div to move horizontally by -50% of its widthtransition
properties:duration:20
: Animation lasts 20 secondsease:'linear'
: Constant speed throughout the animationrepeat:Infinity
: Repeats the animation indefinitely
Tailwind classes on the div:
gap-24
: Adds 24 units of gap between child elementsflex
: Makes it a flex container
flex-none
: Prevents the div from growing or shrinking
The
flex-none
makes sure the elements dont change size when the screen dimensions are changed
pr-24
: Adds 24 units of padding to the right
Note: Its important to add
pr-24
to match thegap-24
, so as to prevent the loop breaking and skipping after the last item of the array. It adds the smooth transition from the first array to the next
{Array.from({length:2}).map((_,i)=>(
<Fragment key={i}>
{logos.map((logo)=>(
<Image src={logo.image} key={logo.id} alt=''
className='w-24'/>
))}
</Fragment>
))}
Array.from({length:2})
: Creates an array with 2 elements.map((_,i)=>())
: Iterates twice, creating a Fragment for each iteration(_, i)
is the parameter list of the callback function._
is a placeholder for the current element in the array.The underscore
_
is a convention used when you don’t care about the actual value of the element.
logos.map
()
: Maps through alogos
array inside each Fragment
Introduction (scroll-driven text reveal animation).
"use client";
import { useScroll, useTransform } from "framer-motion"
import { span } from "framer-motion/client";
import { useEffect, useRef, useState } from "react";
import { twMerge } from "tailwind-merge";
const text = "responsive websites with speed optimization with next js frameworks. We aim to not only meet but exceed our customer's expectations."
const words = text.split(' ');
const Intro = () => {
const scrollRef = useRef(null);
const {scrollYProgress} = useScroll({
target:scrollRef, offset:['start end','end end']
})
const [currentWord,setCurrentWord] = useState(0);
const wordIndex = useTransform(scrollYProgress,[0,1],[0,words.length]);
useEffect(()=>{
wordIndex.on('change',(latest)=>{
setCurrentWord(latest);
})
},[wordIndex])
return (
<div className="py-24 ">
<div className="sticky top-20 md:top-32 lg:top-36">
<div className=" flex justify-center ">
<h1 className=" border border-lime-400 text-lime-400 rounded-full p-2 uppercase text-2xl"> ✶ Introduction</h1>
</div>
<div className="mt-10 text-4xl md:text-5xl lg:text-6xl text-center px-4 md:px-10 lg:px-20 justify-center">
<p className=" text-white font-medium" >
<span className="">Layers is a modern application web service provide with </span>
<span>
{words.map((word,wordIndex)=>(
<span
className={twMerge("transition duration-500 text-white/15",wordIndex<currentWord && 'text-white')}
key={wordIndex}>{`${word} `}</span>
))}
</span>
</p>
<p className=" text-lime-400">Let's connect and work on projects</p>
</div>
</div>
<div className="h-[150vh]" ref={scrollRef}></div>
</div>
)
}
export default Intro
const {scrollYProgress} = useScroll({
target:scrollRef, offset:['start end','end end']
})
The useScroll
hook from Framer Motion tracks the scroll progress of an element, providing a normalized value between 0 and 1 that represents how far the user has scrolled through a specific element.
target: scrollRef
Specifies the DOM element being tracked
scrollRef
is a React reference pointing to the scroll container
In this case, it's the
<div className="h-[150vh]" ref={scrollRef}></div>
element
offset: ['start end', 'end end']
It defines when the scroll progress calculation begins and ends.
First value
'start end'
:When the top of the target element reaches the bottom of the viewport
Marks the starting point of scroll progress (0%)
Second value
'end end'
:When the bottom of the target element reaches the bottom of the viewport
Marks the ending point of scroll progress (100%)
const wordIndex = useTransform(scrollYProgress,[0,1],[0,words.length]);
useEffect(()=>{
wordIndex.on('change',(latest)=>{
setCurrentWord(latest);
})
},[wordIndex])
useTransform
is a powerful Framer Motion hook that maps values from one range to another. It's like a translation device for numeric values.
How It Works Here:
Input Range
[0, 1]
: Represents scroll progress from 0% to 100%Output Range
[0, words.length]
: Converts scroll progress to word indicesAs you scroll, it smoothly converts scroll percentage to a word index
This useEffect()
listens for changes in wordIndex
and updates the currentWord
state accordingly.
Breakdown:
wordIndex.on('change', callback)
: Listens for value changes(latest) => setCurrentWord(latest)
: Updates state with new word index[wordIndex]
: Ensures effect runs whenwordIndex
changes
The key insight is that
useTransform
doesn't just return integer values. It provides a floating-point number that smoothly transitions between 0 andwords.length
.Instead of strictly comparing integers, the condition
wordIndex < currentWord
works because:
currentWord
captures the latest transformed valueThe comparison allows partial word reveals
Floating-point precision enables smooth animations
Services(translate,transition,video).
<div className='mt-12 grid md:grid-cols-2 lg:grid-cols-3 grid-cols-1 gap-8 '>
<div className='bg-neutral-900 p-6 rounded-3xl border border-white/20 group'>
<div className='aspect-video flex items-center justify-center overflow-hidden'>
<div className='z-40 overflow-hidden size-20 border-4 border-lime-400 rounded-full p-1 bg-neutral-900'><Image src={avatar1} alt=''
className='rounded-full'/></div>
<div
className='z-30 -ml-6 overflow-hidden size-20 border-4 border-sky-400 p-1 rounded-full bg-neutral-900'><Image src={avatar2} alt=''
className=' rounded-full '/></div>
<div className='z-20 overflow-hidden -ml-6 size-20 border-4 border-fuchsia-400 rounded-full p-1 bg-neutral-900'>
<Image src={avatar3} alt=''
className='rounded-full '/></div>
<div className='-ml-6 size-20 border-transparent group-hover:border-orange-500 transition relative overflow-hidden rounded-full border-4'>
<Image src={avatar4} alt=""
className='size-full opacity-0 group-hover:opacity-100 absolute rounded-full transition duration-500'
/>
<div className='size-full rounded-full bg-neutral-700 inline-flex justify-center items-center gap-2'>
{Array.from({length:3}).map((_,i)=>(
<span className='size-1.5 rounded-full bg-white inline-flex ' key={i}></span>
))}
</div>
</div>
</div>
<div className=''>
<h3 className='text-2xl font-medium mt-2'> Project management</h3>
<p className='mt-3 text-white/50'>We optimize your webpage for quick and efficient performance</p>
</div>
</div>
<div className='bg-neutral-900 p-6 rounded-3xl border border-white/20'>
<div className='aspect-video flex items-center p-2 text-center relative group'>
<p className='xl:text-6xl text-4xl font-extrabold text-white/20 group-hover:text-white/10 transition duration-500'>
Contributed to <span
className='bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-transparent'>over 100</span>
<video src="/assets/run.mp4"
autoPlay
muted
loop
className='absolute bottom-32 size-36 left-1/2 -translate-x-1/2 rounded-2xl opacity-0 group-hover:opacity-100 transition duration-500 shadow-xl pointer-events-none'
/>
projects
</p>
</div>
<div className=''>
<h3 className='text-2xl font-medium mt-6'> Speed optimization</h3>
<p className='mt-3 text-white/50'>We optimize your webpage for quick and efficient performance</p>
</div>
</div>
<div className='bg-neutral-900 p-6 rounded-3xl border border-white/20 group'>
<div className='aspect-video text-2xl text-black font-medium flex gap-4 items-center justify-center '>
<div
className='w-28 bg-slate-200 border p-2 rounded-2xl outline outline-offset-4 outline-transparent group-hover:outline-lime-400 transition-all duration-500 group-hover:translate-y-2 '>Shift</div>
<div className='w-12 bg-slate-200 border p-2 rounded-2xl outline outline-offset-4 outline-transparent group-hover:outline-lime-400 transition-all duration-500 group-hover:translate-y-2 delay-150 '>C</div>
<div className='w-16 bg-slate-200 border p-2 rounded-2xl outline outline-offset-4 outline-transparent group-hover:outline-lime-400 transition-all duration-500 group-hover:translate-y-2 delay-300'>Crtl</div>
</div>
<div className=''>
<h3 className='text-2xl font-medium mt-6'> Speed optimization</h3>
<p className='mt-3 text-white/50'>We optimize your webpage for quick and efficient performance</p>
</div>
</div>
</div>
<div className='z-40 overflow-hidden size-20 border-4 border-lime-400 rounded-full p-1 bg-neutral-900'><Image src={avatar1} alt=''
className='rounded-full'/></div>
<div
className='z-30 -ml-6 overflow-hidden size-20 border-4 border-sky-400 p-1 rounded-full bg-neutral-900'><Image src={avatar2} alt=''
className=' rounded-full '/></div>
<div className='z-20 overflow-hidden -ml-6 size-20 border-4 border-fuchsia-400 rounded-full p-1 bg-neutral-900'>
<Image src={avatar3} alt=''
className='rounded-full '/></div>
<div className='-ml-6 size-20 border-transparent group-hover:border-orange-500 transition relative overflow-hidden rounded-full border-4'>
<Image src={avatar4} alt=""
className='size-full opacity-0 group-hover:opacity-100 absolute rounded-full transition duration-500'
/>
<div className='size-full rounded-full bg-neutral-700 inline-flex justify-center items-center gap-2'>
{Array.from({length:3}).map((_,i)=>(
<span className='size-1.5 rounded-full bg-white inline-flex ' key={i}></span>
))}
</div>
</div>
z-40
: Highest z-index, ensuring this avatar appears in front
overflow-hidden
: Prevents content from spilling outside the div
Positioning Mechanics:
Negative margin (
-ml-6
) creates a stacked, overlapping effectDecreasing z-index creates a layered appearance
Each avatar slightly peeks out from behind the previous one
<Image
className='size-full opacity-0 group-hover:opacity-100 absolute rounded-full transition duration-500'
/>
opacity-0
: Image hidden by defaultgroup-hover:opacity-100
: Image appears on hoverabsolute
: Positioned relative to parenttransition duration-500
: Smooth fade-in effect
The
group
attribute in Tailwind CSS is a utility class that allows you to target and style child elements based on the state of a parent elementThis is particularly useful when building interactive components like dropdowns, modals, or buttons with hover or focus effects.
Target Child Elements with
group-
Modifiers: You can style child elements based on the state of the parent by using thegroup-
prefix. For example:
group-hover
: Styles the child when the parent is hovered.
group-focus
: Styles the child when the parent is focused
<video src="/assets/run.mp4"
autoPlay
muted
loop
className='absolute bottom-32 size-36 left-1/2 -translate-x-1/2 rounded-2xl opacity-0 group-hover:opacity-100 transition duration-500 shadow-xl pointer-events-none'
/>
autoPlay
: Video starts playing automatically when page loads. No user interaction required to begin playback
muted
: Prevents video audio from playing. Critical for autoplay in most browsers
loop
:Video restarts from the beginning once it reaches the end. Creates a continuous, repeating animation effect
left-1/2
: Moves the video's left edge to the horizontal center of its container. Positions it at 50% of the container's width
-translate-x-1/2
: Negative horizontal translation by 50% .Shifts the video back by half its own width. Ensures perfect horizontal centering. Corrects the positioning from left-1/2
<div className='bg-neutral-900 p-6 rounded-3xl border border-white/20 group'>
<div className='aspect-video text-2xl text-black font-medium flex gap-4 items-center justify-center '>
<div
className='w-28 bg-slate-200 border p-2 rounded-2xl outline outline-offset-4 outline-transparent group-hover:outline-lime-400 transition-all duration-500 group-hover:translate-y-2 '>Shift</div>
<div className='w-12 bg-slate-200 border p-2 rounded-2xl outline outline-offset-4 outline-transparent group-hover:outline-lime-400 transition-all duration-500 group-hover:translate-y-2 delay-150 '>C</div>
<div className='w-16 bg-slate-200 border p-2 rounded-2xl outline outline-offset-4 outline-transparent group-hover:outline-lime-400 transition-all duration-500 group-hover:translate-y-2 delay-300'>Crtl</div>
</div>
<div className=''>
<h3 className='text-2xl font-medium mt-6'> Speed optimization</h3>
<p className='mt-3 text-white/50'>We optimize your webpage for quick and efficient performance</p>
</div>
</div>
outline-offset-4
. Specifies the distance between the outline and the edge of the element (the space between the border and the outline).
Call to action (useAnimate)
const [isHovered,setIsHovered] = useState(false);
const animation = useRef<AnimationPlaybackControls>();
const [scope,animate] = useAnimate();
useEffect(()=>{
animation.current=animate(scope.current,{x:"-50%"},
{ duration: 20,
ease:'linear',
repeat:Infinity});
},[]);
useEffect(()=>{
if(animation.current){
if(isHovered){
animation.current.speed = 0.25;
} else{
animation.current.speed=1;
}
}
},[isHovered]);
animation
Ref: Stores a reference to the animation controls. Allows direct manipulation of animation properties. Uses TypeScript's AnimationPlaybackControls
type for type safety
useAnimate()
Hook: Creates an animation scope
const [scope,animate] = useAnimate();
Returns a tuple with:
scope
:A React ref pointing to the target animation element. Used to define the specific DOM element to animate. Allows precise targeting of animations<motion.div ref={scope} className='flex flex-none gap-16 pr-16 text-8xl' onMouseEnter={()=>setIsHovered(true)} onMouseLeave={()=>setIsHovered(false)} >
animate
: is used to define animation states and apply them to DOM elements.It simplifies animating elements by managing their states and lifecycle in React, making animations declarative and easier to implement.Signature:
animate(target, definition, options)
target
: The element to animate (usesscope.current
)definition
: Animation properties. Like the scale, opacity, rotate and etc. Any simultaneous transformations.options
: Animation configuration. The transition properties for the animation
useEffect(()=>{
animation.current=animate(scope.current,{x:"-50%"},
{ duration: 20,
ease:'linear',
repeat:Infinity});
},[]);