├── 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 | 
28 |
29 | 2. **Countdown Page**
30 | 
31 |
32 | 3. **Happy Birthday Message Screen**
33 | 
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 |
111 |
112 | {/* Decorative elements */}
113 |
114 | {bubbles.map((bubble, i) => (
115 |
126 |
130 |
131 | ))}
132 |
133 |
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 |
--------------------------------------------------------------------------------