Code
app/page.tsx
1import LogoCloud from "@/sections/logo-cloud";
2
3const Home = () => {
4 return (
5 <div className=" w-full h-screen container mx-auto flex items-center justify-center">
6 <LogoCloud />
7 </div>
8 );
9}
10
11export default Home;
Installation
Initialized a project and installed the necessary dependencies to use this component.
1. Initialize a new Next.js project:
npx create-next-app@latest my-app
2. Install Framer Motion
npm install framer-motion --save
Add Logo Cloud Grid Section
src/sections/logo-cloud.tsx
1"use client"
2
3import { Marquee } from "@/components/marquee";
4import { logoClouds, logoCloudsMobile } from "@/constants";
5import { ChevronRight } from "lucide-react";
6import Image from "next/image";
7import Link from "next/link";
8import { motion } from "framer-motion";
9
10interface LogoCloudProps {
11 isTransparent?: boolean;
12}
13
14const LogoCloud = ({ isTransparent = false }: LogoCloudProps) => {
15 return (
16 <section className=" w-full py-12 z-20 relative">
17 <div className=" container hidden md:block mx-auto w-full">
18 <div className="max-w-4xl mx-auto grid md:grid-cols-4 w-full gap-y-7 px-12 py-6 relative group">
19 <div className={`absolute inset-0 ${isTransparent ? 'bg-transparent backdrop-blur-sm' : 'bg-black/60 backdrop-blur-xs'} z-20 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-all duration-300 ease-in-out`}>
20 <Link href="/showcase">
21 <span className="flex text-white hover:text-white/70 items-center gap-1 text-[15px] transform translate-y-4 group-hover:translate-y-0 transition-transform duration-500 ease-in-out">
22 <p>Companies using Astrae</p>
23 <ChevronRight className=" size-5" />
24 </span>
25 </Link>
26 </div>
27 {logoClouds.map((logo, index) => (
28 <motion.div
29 key={index}
30 className="relative h-12 w-[180px]"
31 initial={{ opacity: 0, y: 20 }}
32 whileInView={{ opacity: 1, y: 0 }}
33 transition={{
34 duration: 0.5,
35 delay: index * 0.1,
36 ease: "easeOut"
37 }}
38 viewport={{ once: true, amount: 0.3 }}
39 >
40 <Image fill src={logo.image} alt={logo.name} className="object-contain" />
41 </motion.div>
42 ))}
43 </div>
44 </div>
45 <div className=" w-full relative md:hidden">
46 <div className="absolute top-0 bottom-0 left-0 w-[20%] z-[1] bg-gradient-to-r from-black to-transparent" />
47 <div className="absolute top-0 bottom-0 right-0 w-[20%] z-[1] bg-gradient-to-l from-black to-transparent" />
48 <Marquee className="[--gap:-0rem]">
49 <div className="flex items-center">
50 {logoCloudsMobile.map((logo, index) => (
51 <div key={index} className="relative h-9 w-[148px]">
52 <Image fill src={logo.image} alt={logo.name} className="object-contain" />
53 </div>
54 ))}
55 </div>
56 </Marquee>
57 </div>
58 </section>
59 );
60}
61
62export default LogoCloud;
Add Marquee Component
src/components/marquee.tsx
1import { cn } from "@/lib/utils";
2import { ComponentPropsWithoutRef } from "react";
3
4interface MarqueeProps extends ComponentPropsWithoutRef<"div"> {
5 /**
6 * Optional CSS class name to apply custom styles
7 */
8 className?: string;
9 /**
10 * Whether to reverse the animation direction
11 * @default false
12 */
13 reverse?: boolean;
14 /**
15 * Whether to pause the animation on hover
16 * @default false
17 */
18 pauseOnHover?: boolean;
19 /**
20 * Content to be displayed in the marquee
21 */
22 children: React.ReactNode;
23 /**
24 * Whether to animate vertically instead of horizontally
25 * @default false
26 */
27 vertical?: boolean;
28 /**
29 * Number of times to repeat the content
30 * @default 4
31 */
32 repeat?: number;
33}
34
35export function Marquee({
36 className,
37 reverse = false,
38 pauseOnHover = false,
39 children,
40 vertical = false,
41 repeat = 4,
42 ...props
43}: MarqueeProps) {
44 return (
45 <div
46 {...props}
47 className={cn(
48 "group flex overflow-hidden z-20 p-2 [--duration:40s] [--gap:1rem] [gap:var(--gap)]",
49 {
50 "flex-row": !vertical,
51 "flex-col": vertical,
52 },
53 className,
54 )}
55 >
56 {Array(repeat)
57 .fill(0)
58 .map((_, i) => (
59 <div
60 key={i}
61 className={cn("flex [gap:var(--gap)]", {
62 "animate-marquee flex-row": !vertical,
63 "animate-marquee-vertical flex-col": vertical,
64 "group-hover:[animation-play-state:paused]": pauseOnHover,
65 "[animation-direction:reverse]": reverse,
66 })}
67 >
68 {children}
69 </div>
70 ))}
71 </div>
72 );
73}
Add Constants
src/constants/index.ts
1export const logoClouds = [
2 {
3 name: "Linear",
4 image: "/assets/company-logo-1.png",
5 },
6 {
7 name: "Splunk",
8 image: "/assets/company-logo-2.png",
9 },
10 {
11 name: "Dropbox",
12 image: "/assets/company-logo-3.png",
13 },
14 {
15 name: "Hopin",
16 image: "/assets/company-logo-4.png",
17 },
18 {
19 name: "Zapier",
20 image: "/assets/company-logo-5.png",
21 },
22 {
23 name: "Attentive",
24 image: "/assets/company-logo-6.png",
25 },
26 {
27 name: "Rippling",
28 image: "/assets/company-logo-7.png",
29 },
30 {
31 name: "Descript",
32 image: "/assets/company-logo-8.png",
33 },
34];
35
36export const logoCloudsMobile = [
37 {
38 name: "Linear",
39 image: "/assets/c-logo-1.png",
40 },
41 {
42 name: "Splunk",
43 image: "/assets/c-logo-2.png",
44 },
45 {
46 name: "Dropbox",
47 image: "/assets/c-logo-3.png",
48 },
49 {
50 name: "Hopin",
51 image: "/assets/c-logo-4.png",
52 },
53 {
54 name: "Zapier",
55 image: "/assets/c-logo-5.png",
56 },
57 {
58 name: "Attentive",
59 image: "/assets/c-logo-6.png",
60 },
61 {
62 name: "Rippling",
63 image: "/assets/c-logo-7.png",
64 },
65 {
66 name: "Descript",
67 image: "/assets/c-logo-8.png",
68 },
69];