Skip to content

Instantly share code, notes, and snippets.

@Vetrivel-VP
Last active July 10, 2025 09:36
Show Gist options
  • Save Vetrivel-VP/9802648f318ef82788e52244a3edad9d to your computer and use it in GitHub Desktop.
Save Vetrivel-VP/9802648f318ef82788e52244a3edad9d to your computer and use it in GitHub Desktop.
AI ROom Planner
hero.tsx
---------------------------------------------------------------------------------------------------------------------
"use client";
import React, { useRef } from "react";
import { Container } from "@/components/container";
import { GenerateButton } from "../generate-button";
import Link from "next/link";
import starsBg from "@/assets/stars.png";
import {
motion,
useMotionValueEvent,
useScroll,
useTransform,
} from "framer-motion";
interface HeroProps {
isAuthenticated: boolean;
userId: string | null;
}
export const Hero = ({ isAuthenticated, userId }: HeroProps) => {
const sectionRef = useRef(null);
const { scrollYProgress } = useScroll({
target: sectionRef,
offset: ["start end", "end start"],
});
// useMotionValueEvent(scrollYProgress, "change", (value) => {
// console.log("scrollYProgress", value);
// }); =
const backgroundPositionY = useTransform(
scrollYProgress,
[0, 1],
[-300, 300]
);
return (
<motion.section
ref={sectionRef}
style={{ backgroundImage: `url(${starsBg.src})`, backgroundPositionY }}
animate={{ backgroundPositionX: starsBg.width }}
transition={{ duration: 30, repeat: Infinity, ease: "linear" }}
className="h-[492px] md:h-[800px] flex items-center overflow-hidden relative [mask-image:linear-gradient(to_bottom,transparent,black_10%,black_90%,transparent)]"
>
{/* absolute circle layers */}
<div className="absolute inset-0 bg-[radial-gradient(75%_75%_at_center_center,rgba(140,69,255,.5)_15%,rgb(14,0,36,.5)_78%,transparent)]"></div>
{/* start planet */}
<div className="absolute h-64 w-64 md:h-96 md:w-96 bg-purple-500 rounded-full border border-white/30 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-[radial-gradient(50%_50%_at_16.8%_18.3%,white,rgb(184,148,255)_37.7%,rgb(24,0,66))] shadow-[-20px_-20px_50px_rgba(255,255,255,.5),-20px_-20px_80px_rgba(255,255,255,.1),0_0_50pc_rgb(140,69,255)]"></div>
{/* end planet */}
{/* ring 1 */}
<motion.div
animate={{ rotate: "1turn" }}
transition={{ duration: 30, repeat: Infinity, ease: "linear" }}
className="absolute h-[344px] w-[344px] min-md:w-[580px] min-md:h-[580px] border border-white opacity-20 rounded-full top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 "
>
<div className="absolute h-2 w-2 left-0 bg-white rounded-full top-1/2 -translate-x-1/2 -translate-y-1/2 "></div>
<div className="absolute h-2 w-2 left-1/2 bg-white rounded-full top-0 -translate-x-1/2 -translate-y-1/2 "></div>
<div className="absolute h-5 w-5 left-full border border-white rounded-full top-1/2 -translate-x-1/2 -translate-y-1/2 inline-flex items-center justify-center">
<div className="h-2 w-2 bg-white rounded-full"></div>
</div>
</motion.div>
{/* ring 2 */}
<motion.div
animate={{ rotate: "-1turn" }}
transition={{ repeat: Infinity, duration: 60, ease: "linear" }}
className="absolute h-[444px] w-[444px] min-md:h-[780px] min-md:w-[780px] rounded-full border border-white/20 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 border-dashed"
></motion.div>
{/* ring 3 */}
<motion.div
animate={{ rotate: "1turn" }}
transition={{ repeat: Infinity, duration: 30, ease: "linear" }}
className="absolute h-[544px] w-[544px] min-md:w-[980px] min-md:h-[980px] rounded-full border border-white opacity-20 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
>
<div className="absolute h-2 w-2 left-0 bg-white rounded-full top-1/2 -translate-x-1/2 -translate-y-1/2 "></div>
<div className="absolute h-2 w-2 left-full bg-white rounded-full top-1/2 -translate-x-1/2 -translate-y-1/2 "></div>
</motion.div>
{/* create this first */}
<Container className="relative mt-16">
<h1 className="text-7xl md:text-[128px] details-content:leading-none capitalize font-semibold tracking-tighter bg-white bg-[radial-gradient(100%_100%_at_top_left,white,white,rgba(74,32,138,.5))] text-transparent bg-clip-text text-center">
AI Planner
</h1>
<p className="text-lg min-md:text-xl max-w-xl mx-auto text-white/70 mt-5 text-center">
Elevate your room's visibility effortlessly with AI, where smart
technology meets intuitive design.
</p>
<div className="flex justify-center mt-5">
<Link href={isAuthenticated ? "/dashboard" : "/sign-in"}>
<GenerateButton label="Generate Room" />
</Link>
</div>
</Container>
</motion.section>
);
};
---------------------------------------------------------------------------------------------------------------------
logo-ticker.tsx
---------------------------------------------------------------------------------------------------------------------
"use client";
import React from "react";
import { Container } from "@/components/container";
import Image from "next/image";
import { motion } from "framer-motion";
const logos = [
"/assets/img/logo/firebase.png",
"/assets/img/logo/googlecloud.png",
"/assets/img/logo/nextjs.png",
"/assets/img/logo/prisma.png",
"/assets/img/logo/react.png",
"/assets/img/logo/nextjs.png",
"/assets/img/logo/firebase.png",
"/assets/img/logo/prisma.png",
"/assets/img/logo/firebase.png",
"/assets/img/logo/googlecloud.png",
"/assets/img/logo/nextjs.png",
"/assets/img/logo/prisma.png",
"/assets/img/logo/react.png",
"/assets/img/logo/nextjs.png",
"/assets/img/logo/firebase.png",
"/assets/img/logo/prisma.png",
];
export const LogoTicker = () => {
return (
<section className="py-20 md:py-24">
<Container className="p-4 md:p-8">
<div className="flex items-center gap-5">
<div className="flex-1 md:flex-none">
<h2>Powered by Innovative Techs</h2>
</div>
<div className="flex-1 overflow-hidden [mask-image:linear-gradient(to_right,transparent,black_20%,black_80%,transparent)]">
<motion.div
initial={{ translateX: "-50%" }}
animate={{ translateX: "0" }}
transition={{ repeat: Infinity, duration: 20, ease: "linear" }}
className="flex flex-none gap-8 -translate-x-1/2 md:pr-8"
>
{logos.map((logo, index) => (
<Image
key={index}
src={logo}
alt={`Tech logo ${index + 1}`}
width={100}
height={24}
className="h-12 w-auto grayscale invert brightness-0 transition"
/>
))}
</motion.div>
</div>
</div>
</Container>
</section>
);
};
---------------------------------------------------------------------------------------------------------------------
features.tsx
---------------------------------------------------------------------------------------------------------------------
"use client";
import React, { useEffect, useRef } from "react";
import { Container } from "@/components/container";
import {
DotLottieCommonPlayer,
DotLottiePlayer,
} from "@dotlottie/react-player";
import Image from "next/image";
import {
useMotionTemplate,
useMotionValue,
motion,
animate,
ValueAnimationTransition,
} from "framer-motion";
interface TabsProps {
icon: string;
title: string;
isNew: boolean;
backgroundPositionX: number;
backgroundPositionY: number;
backgroundSizeX: number;
}
const tabs: TabsProps[] = [
{
icon: "/assets/lottie/vroom.lottie",
title: "Clean & Intuitive Dashboard",
isNew: false,
backgroundPositionX: 0,
backgroundPositionY: 0,
backgroundSizeX: 150,
},
{
icon: "/assets/lottie/click.lottie",
title: "One Click Redesign",
isNew: true,
backgroundPositionX: 0,
backgroundPositionY: 0,
backgroundSizeX: 150,
},
{
icon: "/assets/lottie/stars.lottie",
title: "AI-Powered Image Analysis",
isNew: false,
backgroundPositionX: 0,
backgroundPositionY: 0,
backgroundSizeX: 150,
},
];
export const Features = () => {
return (
<section className="py-20 md:py-24">
<Container className="p-4 md:p-8">
<h2 className="text-5xl md:text-6xl font-medium text-center tracking-tighter">
Transform Your Room with AI
</h2>
<p className="text-white/70 text-lg md:text-lg max-w-2xl mx-auto tracking-tight text-center mt-5">
Upload any room image and let our intelligent design engine analyze
and reimagine your space like never before.
</p>
<div className="mt-10 flex flex-col lg:flex-row gap-3">
{tabs.map((tab) => (
<FeatureTab key={tab.title} {...tab} />
))}
</div>
<div className="border border-white/20 p-2.5 rounded-xl mt-8">
<div
className="aspect-video bg-cover bg-no-repeat bg-center border border-white/20 rounded-lg"
style={{ backgroundImage: "url(/assets/img/features.jpg)" }}
></div>
</div>
</Container>
</section>
);
};
const FeatureTab = (tab: TabsProps) => {
const dotLottieRef = useRef<DotLottieCommonPlayer>(null);
const xPerencetage = useMotionValue(0);
const yPerecetage = useMotionValue(0);
const tabRef = useRef<HTMLDivElement>(null);
const maskImage = useMotionTemplate`radial-gradient(80px 80px at ${xPerencetage}% ${yPerecetage}%, black ,transparent)`;
useEffect(() => {
if (!tabRef.current) return;
const { height, width } = tabRef.current?.getBoundingClientRect();
const circumference = height * 2 + width * 2;
const times = [
0,
width / circumference,
(width + height) / circumference,
(width * 2 + height) / circumference,
1,
];
const options: ValueAnimationTransition = {
times,
duration: 4,
repeat: Infinity,
ease: "linear",
repeatType: "loop",
};
animate(xPerencetage, [0, 100, 100, 0, 0], options);
animate(yPerecetage, [0, 0, 100, 100, 0], options);
}, []);
const handleTabHover = () => {
if (dotLottieRef.current === null) return;
// push the animation to starting to run one more time
dotLottieRef.current.seek(0);
dotLottieRef.current.play();
};
return (
<div
ref={tabRef}
onMouseEnter={handleTabHover}
className="border border-white/15 flex p-2.5 rounded-xl gap-2.5 items-center lg:flex-1 relative"
>
<motion.div
style={{ maskImage }}
className="absolute inset-0 -m-px rounded-lg border border-[#A369FF]"
></motion.div>
<div className="h-12 w-12 border border-white/15 rounded-lg inline-flex items-center justify-center">
<DotLottiePlayer
ref={dotLottieRef}
src={tab.icon}
className="h-5 w-5"
// autoplay
/>
</div>
<div className="font-medium">{tab.title}</div>
{tab.isNew && (
<div className="text-xs rounded-full px-2 py-0.5 bg-[#8c44ff] text-black font-semibold">
new
</div>
)}
</div>
);
};
---------------------------------------------------------------------------------------------------------------------
footer.tsx
---------------------------------------------------------------------------------------------------------------------
"use client";
import React from "react";
import { LogoContainer } from "@/components/logo-container";
import Link from "next/link";
import { Instagram, Linkedin, Youtube } from "lucide-react";
import { Container } from "@/components/container";
export const Footer = () => {
return (
<footer className="py-5 border-t border-white/15">
<Container className="p-4 md:px-12 md:py-2">
<div className="flex flex-col lg:flex-row items-center gap-8 ">
<div className="flex float-start lg:flex-1">
<LogoContainer />
</div>
<nav className="flex flex-col lg:flex-row items-center gap-5 lg:flex-1 lg:justify-center">
<Link
href={"#"}
className="text-white/70 hover:text-white text-xs md:text-sm transition"
>
Features
</Link>
<Link
href={"#"}
className="text-white/70 hover:text-white text-xs md:text-sm transition"
>
Developers
</Link>
<Link
href={"#"}
className="text-white/70 hover:text-white text-xs md:text-sm transition"
>
Company
</Link>
<Link
href={"#"}
className="text-white/70 hover:text-white text-xs md:text-sm transition"
>
Blog
</Link>
<Link
href={"#"}
className="text-white/70 hover:text-white text-xs md:text-sm transition"
>
Changelog
</Link>
</nav>
<div className="flex gap-5 items-center lg:flex-1 lg:justify-end">
<Instagram className="text-white/40 hover:text-white transition" />
<Linkedin className="text-white/40 hover:text-white transition" />
<Youtube className="text-white/40 hover:text-white transition" />
</div>
</div>
</Container>
</footer>
);
};
---------------------------------------------------------------------------------------------------------------------
.env
---------------------------------------------------------------------------------------------------------------------
# .env.local
NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_project_id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_project.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=your_measurement_id # Optional (Analytics)
---------------------------------------------------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment