├── public ├── ss1.png ├── ss2.png ├── ss3.png └── birthday.mp3 ├── src ├── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.js │ └── page.jsx └── components │ ├── floating-hearts.jsx │ ├── confetti.jsx │ ├── countdown.jsx │ ├── Loader.jsx │ └── birthday-celebration.jsx ├── jsconfig.json ├── postcss.config.mjs ├── next.config.mjs ├── eslint.config.mjs ├── package.json ├── .gitignore └── README.md /public/ss1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manis-sharma/birthday-site/HEAD/public/ss1.png -------------------------------------------------------------------------------- /public/ss2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manis-sharma/birthday-site/HEAD/public/ss2.png -------------------------------------------------------------------------------- /public/ss3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manis-sharma/birthday-site/HEAD/public/ss3.png -------------------------------------------------------------------------------- /public/birthday.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manis-sharma/birthday-site/HEAD/public/birthday.mp3 -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manis-sharma/birthday-site/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | 5 | 6 | 7 | 8 | export default nextConfig; 9 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [...compat.extends("next/core-web-vitals")]; 13 | 14 | export default eslintConfig; 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | :root { 4 | --background: #ffffff; 5 | --foreground: #171717; 6 | } 7 | 8 | @theme inline { 9 | --color-background: var(--background); 10 | --color-foreground: var(--foreground); 11 | --font-sans: var(--font-geist-sans); 12 | --font-mono: var(--font-geist-mono); 13 | } 14 | 15 | @media (prefers-color-scheme: dark) { 16 | :root { 17 | --background: #0a0a0a; 18 | --foreground: #ededed; 19 | } 20 | } 21 | 22 | body { 23 | background: var(--background); 24 | color: var(--foreground); 25 | font-family: "Shantell Sans", cursive; 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "birthday-special", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "framer-motion": "^12.7.4", 13 | "lucide-react": "^0.488.0", 14 | "next": "15.3.0", 15 | "react": "^19.0.0", 16 | "react-dom": "^19.0.0" 17 | }, 18 | "devDependencies": { 19 | "@eslint/eslintrc": "^3", 20 | "@tailwindcss/postcss": "^4", 21 | "eslint": "^9", 22 | "eslint-config-next": "15.3.0", 23 | "tailwindcss": "^4" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | -------------------------------------------------------------------------------- /src/app/layout.js: -------------------------------------------------------------------------------- 1 | import { Geist, Geist_Mono } from "next/font/google"; 2 | import "./globals.css"; 3 | 4 | const geistSans = Geist({ 5 | variable: "--font-geist-sans", 6 | subsets: ["latin"], 7 | }); 8 | 9 | const geistMono = Geist_Mono({ 10 | variable: "--font-geist-mono", 11 | subsets: ["latin"], 12 | }); 13 | 14 | export const metadata = { 15 | title: "Happy Birthday!", 16 | description: "A special birthday countdown and celebration", 17 | } 18 | 19 | export default function RootLayout({ children }) { 20 | return ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | {children} 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/components/floating-hearts.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useEffect, useState } from "react" 4 | import { motion } from "framer-motion" 5 | import { Heart } from "lucide-react" 6 | 7 | export default function FloatingHearts() { 8 | const [hearts, setHearts] = useState([]) 9 | 10 | useEffect(() => { 11 | // Generate floating hearts 12 | const colors = [ 13 | "text-pink-500", 14 | "text-pink-400", 15 | "text-pink-300", 16 | "text-red-400", 17 | "text-purple-400", 18 | "text-purple-300", 19 | ] 20 | 21 | const fills = ["fill-pink-200", "fill-pink-100", "fill-red-100", "fill-purple-100"] 22 | 23 | const newHearts = Array.from({ length: 12 }).map((_, i) => ({ 24 | id: i, 25 | x: Math.random() * 100, 26 | y: Math.random() * 100, 27 | size: 16 + Math.random() * 24, 28 | color: colors[Math.floor(Math.random() * colors.length)], 29 | fill: fills[Math.floor(Math.random() * fills.length)], 30 | duration: 10 + Math.random() * 20, 31 | delay: Math.random() * 5, 32 | })) 33 | 34 | setHearts(newHearts) 35 | }, []) 36 | 37 | return ( 38 |
39 | {hearts.map((heart) => ( 40 | 60 | 61 | 62 | ))} 63 |
64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Birthday Surprise Website made by manish sharma 🎂🎉 2 | 3 | This is a special **Birthday Celebration Website** created using **Next.js, Tailwind CSS, Framer Motion**, and **Lucide Icons**. 4 | It's designed as a personal and emotional way to wish someone special — when you can't be there physically, let your code speak! 💖 5 | 6 | 7 | --- 8 | 9 | ## 🧠 Project Idea 10 | 11 | > **POV:** It's her birthday, but you can't meet — so you build something special instead. 12 | 13 | The website features: 14 | 15 | - A live countdown timer ⏳ 16 | - Personalized birthday messages 🎈 17 | - Smooth animations using Framer Motion ✨ 18 | - Cute icons and a heartfelt design 💌 19 | 20 | This was created as part of an emotional reel where the journey begins with a few lines of code in VS Code and ends with a beautiful surprise on the browser. 21 | 22 | --- 23 | 24 | ## Screenshots: 25 | 26 | 1. **Loader Page** 27 | ![Screenshot 1](./public/ss1.png) 28 | 29 | 2. **Countdown Page** 30 | ![Screenshot 2](./public/ss2.png) 31 | 32 | 3. **Happy Birthday Message Screen** 33 | ![Screenshot 3](./public/ss3.png) 34 | 35 | --- 36 | 37 | ## 🛠️ Built With 38 | 39 | - [Next.js](https://nextjs.org/) 40 | - [Tailwind CSS](https://tailwindcss.com/) 41 | - [Framer Motion](https://www.framer.com/motion/) 42 | - [Lucide Icons](https://lucide.dev/) 43 | 44 | --- 45 | 46 | ## 🔧 Setup 47 | 48 | To run this project locally: 49 | 50 | ```bash 51 | git clone https://github.com/Anuj579/birthday-site.git 52 | cd birthday-site 53 | npm install 54 | npm run dev 55 | ``` 56 | 57 | Make sure to update the target date in `Home` component if you want to reuse this. 58 | 59 | --- 60 | 61 | ## 🌐 Connect with Me 62 | 63 | Follow for more such creative and code-based content! 64 | 65 | - 📸 **Instagram**: [Manish](https://www.instagram.com/manish_sharmaa45/) 66 | - 🎥 **Facebook**: [Manish](https://www.facebook.com/profile.php?id=100086450979626) 67 | 68 | 69 | --- 70 | 71 | Thanks for checking out this project! If you liked it, consider giving it a ⭐️ on GitHub and sharing the reel ❤️ 72 | -------------------------------------------------------------------------------- /src/components/confetti.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useEffect, useState } from "react" 4 | import { motion } from "framer-motion" 5 | 6 | export default function Confetti() { 7 | const [confetti, setConfetti] = useState([]) 8 | 9 | useEffect(() => { 10 | // Generate confetti pieces 11 | const colors = [ 12 | "bg-rose-500", 13 | "bg-purple-500", 14 | "bg-yellow-400", 15 | "bg-blue-400", 16 | "bg-green-400", 17 | "bg-red-400", 18 | "bg-pink-300", 19 | "bg-purple-300", 20 | ] 21 | 22 | const shapes = ["rounded-full", "rounded", "rounded-sm", "heart-shape"] 23 | 24 | const newConfetti = Array.from({ length: 70 }).map((_, i) => ({ 25 | id: i, 26 | x: Math.random() * 100, 27 | y: -10 - Math.random() * 10, 28 | size: 5 + Math.random() * 10, 29 | color: colors[Math.floor(Math.random() * colors.length)], 30 | shape: shapes[Math.floor(Math.random() * shapes.length)], 31 | duration: 3 + Math.random() * 5, 32 | delay: Math.random() * 5, 33 | })) 34 | 35 | setConfetti(newConfetti) 36 | }, []) 37 | 38 | return ( 39 |
40 | {confetti.map((piece) => ( 41 | 0.5 ? 1 : -1)], 59 | }} 60 | transition={{ 61 | duration: piece.duration, 62 | delay: piece.delay, 63 | repeat: Number.POSITIVE_INFINITY, 64 | ease: "linear", 65 | }} 66 | /> 67 | ))} 68 |
69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /src/components/countdown.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState, useEffect } from "react" 4 | import { motion } from "framer-motion" 5 | import { Heart, Gift, Cake, Star } from "lucide-react" 6 | 7 | function calculateTimeLeft(targetDate) { 8 | const difference = targetDate - new Date() 9 | let timeLeft = {} 10 | 11 | if (difference > 0) { 12 | timeLeft = { 13 | days: Math.floor(difference / (1000 * 60 * 60 * 24)), 14 | hours: Math.floor((difference / (1000 * 60 * 60)) % 24), 15 | minutes: Math.floor((difference / 1000 / 60) % 60), 16 | seconds: Math.floor((difference / 1000) % 60), 17 | } 18 | } 19 | 20 | return timeLeft 21 | } 22 | 23 | export default function Countdown({ targetDate, onCountdownEnd }) { 24 | const [timeLeft, setTimeLeft] = useState(calculateTimeLeft(targetDate)) 25 | 26 | useEffect(() => { 27 | const timer = setTimeout(() => { 28 | const updated = calculateTimeLeft(targetDate) 29 | 30 | setTimeLeft(updated) 31 | if (!updated || Object.keys(updated).length <= 0) { 32 | onCountdownEnd() 33 | } 34 | }, 1000) 35 | 36 | return () => clearTimeout(timer) 37 | }, [timeLeft, targetDate]) 38 | 39 | const icons = [ 40 | , 41 | , 42 | , 43 | , 44 | ] 45 | 46 | return ( 47 |
48 | 59 | Your Special Day is Almost Here💕 60 | 61 | 62 |
63 | {Object.keys(timeLeft).length > 0 ? ( 64 | Object.entries(timeLeft).map(([unit, value], index) => ( 65 | 73 |
{value}
74 |
{unit}
75 |
{icons[index % icons.length]}
76 |
77 | )) 78 | ) : ( 79 |

It's time!

80 | )} 81 |
82 | 83 | 89 |

90 | Just a little more... A small gift for my favorite person❤️ 91 |

92 | 93 |
94 | {Array.from({ length: 3 }).map((_, i) => ( 95 | 108 | ))} 109 |
110 |
111 |
112 | ) 113 | } 114 | -------------------------------------------------------------------------------- /src/components/Loader.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { motion } from 'framer-motion' 3 | import { Heart, Sparkles, Star } from "lucide-react" 4 | 5 | function Loader() { 6 | const [randomPositions, setRandomPositions] = useState([]); 7 | 8 | // Only run the random position generation after the component mounts 9 | useEffect(() => { 10 | const positions = Array.from({ length: 20 }).map(() => ({ 11 | x: Math.random() * 100, 12 | y: Math.random() * 100, 13 | })); 14 | setRandomPositions(positions); 15 | }, []); 16 | return ( 17 |
18 | {/* Floating background hearts */} 19 |
20 | {randomPositions.map((position, i) => ( 21 | 42 | {i % 5 === 0 ? ( 43 | 46 | ) : i % 5 === 1 ? ( 47 | 50 | ) : ( 51 | 59 | )} 60 | 61 | ))} 62 |
63 | 64 | {/* Main loading card */} 65 | 71 | {/* Bouncing hearts */} 72 |
73 | {Array.from({ length: 3 }).map((_, i) => ( 74 | 86 | 94 | 95 | ))} 96 |
97 | 98 | 108 | Loading something special... 109 | 110 | 111 | {/* Cute emojis */} 112 |
113 | {["🎂", "✨", "🎁", "💖", "🎈"].map((emoji, i) => ( 114 | 128 | {emoji} 129 | 130 | ))} 131 |
132 |
133 |
134 | ) 135 | } 136 | 137 | export default Loader -------------------------------------------------------------------------------- /src/app/page.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState, useEffect, useRef } from "react" 4 | import { motion, AnimatePresence } from "framer-motion" 5 | import Countdown from "@/components/countdown" 6 | import BirthdayCelebration from "@/components/birthday-celebration" 7 | import Confetti from "@/components/confetti" 8 | import FloatingHearts from "@/components/floating-hearts" 9 | import Loader from "@/components/Loader" 10 | import { MoveRight, PartyPopper } from "lucide-react" 11 | 12 | export default function Home() { 13 | const [isBirthday, setIsBirthday] = useState(false) 14 | const [isLoading, setIsLoading] = useState(true) 15 | const [bubbles, setBubbles] = useState([]) 16 | const [showForYouBtn, setShowForYouBtn] = useState(false) 17 | const birthdayDate = new Date("April 28, 2025") // Change this date accordingly 18 | const audioRef = useRef(null) 19 | 20 | // For testing 21 | // const birthdayDate = new Date("2025-04-23T22:03:00+05:30") 22 | 23 | useEffect(() => { 24 | setTimeout(() => { 25 | setIsLoading(false) 26 | }, 1500); 27 | }, []) 28 | 29 | const startCelebration = () => { 30 | setShowForYouBtn(false) 31 | setIsBirthday(true) 32 | // Play the song 33 | if (audioRef.current) { 34 | audioRef.current.volume = 0.8; 35 | audioRef.current.play().catch((e) => { 36 | console.log("Autoplay prevented, user interaction needed", e) 37 | }) 38 | } 39 | } 40 | 41 | useEffect(() => { 42 | const generated = Array.from({ length: 20 }).map(() => ({ 43 | left: `${Math.random() * 100}%`, 44 | top: `${Math.random() * 100}%`, 45 | color: ["bg-pink-300", "bg-purple-300", "bg-yellow-300", "bg-violet-300", "bg-rose-300"][Math.floor(Math.random() * 3)], 46 | size: 16 + Math.floor(Math.random() * 8), 47 | duration: 3 + Math.random() * 2, 48 | delay: Math.random() * 5 49 | })) 50 | setBubbles(generated) 51 | }, []) 52 | 53 | if (isLoading) { 54 | return ( 55 | 56 | ) 57 | } 58 | 59 | return ( 60 |
61 | {isBirthday && } 62 | 63 | 64 | 70 | 73 | 74 | {isBirthday ? ( 75 | 76 | ) : ( 77 | setShowForYouBtn(true)} /> 78 | )} 79 | 80 | 81 | 82 | 83 | {showForYouBtn && 90 | 103 | 104 | For you 105 | 106 | 107 | } 108 | 109 | {/* You can change the background song if you want */} 110 |
134 | ) 135 | } 136 | -------------------------------------------------------------------------------- /src/components/birthday-celebration.jsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState } from "react" 4 | import { AnimatePresence, motion } from "framer-motion" 5 | import { Heart, Sparkles, Gift, Cake } from "lucide-react" 6 | 7 | export default function BirthdayCelebration() { 8 | const [isCardOpen, setIsCardOpen] = useState(false) 9 | 10 | return ( 11 |
12 | 23 |

Happy Birthday!

24 |
25 | 26 | 27 | 28 |
29 |

To My Cutiepie

30 |
31 | 32 | 38 |
setIsCardOpen(!isCardOpen)} 42 | > 43 |
47 |
48 | 52 | 53 | 54 |
55 | 56 |
57 |

Tap to {isCardOpen ? "close" : "open"} your card

58 |
59 | 69 | 70 | 71 |
72 |
73 |
74 | 75 | {/* Card content */} 76 | 77 | {isCardOpen && 89 |
90 |

91 | Just wanted to remind you—you're my favorite person. My days are better, smiles are wider, and life is sweeter because of you. 92 |

93 |

I hope your birthday is full of love, magic, and everything that makes you smile 💖

94 |
95 | 104 | 105 | 106 |
107 |
108 |
} 109 |
110 |
111 |
112 | 113 | 119 |
120 |

121 | May every wish you make today come true. You deserve the world, and I’ll always be here to remind you of that. 122 |

123 |
124 |

Let’s always stay like this... together, forever☺

125 |
126 |
127 |
128 |
129 | ) 130 | } 131 | --------------------------------------------------------------------------------