Last active
July 10, 2025 09:36
-
-
Save Vetrivel-VP/9802648f318ef82788e52244a3edad9d to your computer and use it in GitHub Desktop.
AI ROom Planner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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