Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions Domains/Frontend/MiniProjects/BirthdayReminder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# 🎂 Birthday Reminder App

**Contributor:** [Tanmay-Kad](https://github.com/Tanmay-Kad)

---

## 🧾 Description
A **modern and interactive Birthday Reminder web app** that helps you keep track of your loved ones' birthdays effortlessly.
This app lets you add, view, and delete birthdays — with automatic countdowns and a **confetti celebration** when it’s someone’s birthday! 🎉

Built with **HTML**, **CSS (Tailwind)**, and **JavaScript**, it provides a smooth and elegant user experience with local storage support to remember all your entries.

---

## 🚀 Features
- Add birthdays with **name** and **date**.
- Displays countdown for **upcoming birthdays**.
- Automatic sorting of birthdays (nearest first).
- Beautiful **confetti animation** when it's someone's birthday 🎊.
- Option to **delete birthdays** anytime.
- Data stored using **LocalStorage** (persists even after refreshing).
- Fully responsive and modern UI using **Tailwind CSS**.

---

## 💡 Bonus Features
- 🎈 Confetti animation for birthday celebrations.
- 📅 Automatic day calculation for each entry.
- ✨ Smooth hover effects and transitions.
- 🗑️ Slide-in delete button on hover.

---

## 🧩 Tech Stack
- **HTML5** – Structure and layout
- **Tailwind CSS** – Styling and responsiveness
- **JavaScript (Vanilla)** – Logic, animations, and LocalStorage

---

## 🕹️ How to Use
1. Open `index.html` in your browser.
2. Enter a **name** and **birthdate**.
3. Click **“Add Birthday”**.
4. View the countdown for upcoming birthdays.
5. Watch the confetti rain down when it’s someone’s special day! 🎉
6. Use the **delete button** (🗑️) to remove any entry.

---
49 changes: 49 additions & 0 deletions Domains/Frontend/MiniProjects/BirthdayReminder/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Birthday Reminder</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body class="bg-gradient-to-br from-rose-100 to-teal-100 min-h-screen flex items-center justify-center p-4">

<canvas id="confetti-canvas"></canvas>

<div class="w-full max-w-md mx-auto bg-white/80 backdrop-blur-sm rounded-2xl shadow-xl p-6 md:p-8">
<div class="text-center mb-6">
<h1 class="text-3xl font-bold text-gray-800">Birthday Countdown</h1>
<p class="text-gray-500 mt-2">Never miss a special day again!</p>
</div>

<!-- Input Form -->
<div class="space-y-4">
<div>
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">Name</label>
<input type="text" id="name" placeholder="e.g., Jane Doe" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-rose-400 focus:border-rose-400 transition">
</div>
<div>
<label for="birthdate" class="block text-sm font-medium text-gray-700 mb-1">Birthdate</label>
<input type="date" id="birthdate" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-rose-400 focus:border-rose-400 transition">
</div>
<button id="add-birthday-btn" class="w-full bg-rose-500 text-white font-bold py-3 px-4 rounded-lg hover:bg-rose-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rose-500 transform hover:scale-105 transition-transform duration-300 shadow-md">
Add Birthday
</button>
</div>

<!-- Birthday List -->
<div class="mt-8">
<h2 class="text-xl font-semibold text-gray-700 border-b pb-2 mb-4">Upcoming Birthdays</h2>
<div id="birthday-list" class="space-y-3 max-h-64 overflow-y-auto pr-2">
<!-- Birthday items will be injected here -->
</div>
</div>
</div>

<script src="script.js"></script>
</body>
</html>
178 changes: 178 additions & 0 deletions Domains/Frontend/MiniProjects/BirthdayReminder/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
const nameInput = document.getElementById('name');
const birthdateInput = document.getElementById('birthdate');
const addBtn = document.getElementById('add-birthday-btn');
const birthdayList = document.getElementById('birthday-list');
const confettiCanvas = document.getElementById('confetti-canvas');

// --- Confetti Logic ---
const ctx = confettiCanvas.getContext('2d');
let confettiParticles = [];
const colors = ["#f43f5e", "#ec4899", "#d946ef", "#a855f7", "#8b5cf6", "#6366f1", "#3b82f6", "#0ea5e9", "#06b6d4", "#14b8a6"];

function startConfetti() {
confettiCanvas.style.display = 'block';
confettiCanvas.width = window.innerWidth;
confettiCanvas.height = window.innerHeight;
confettiParticles = [];
for (let i = 0; i < 200; i++) {
confettiParticles.push({
x: Math.random() * confettiCanvas.width,
y: Math.random() * -confettiCanvas.height,
size: Math.random() * 8 + 4,
color: colors[Math.floor(Math.random() * colors.length)],
speed: Math.random() * 3 + 2,
angle: Math.random() * Math.PI * 2,
tilt: Math.random() * 10 - 5
});
}
animateConfetti();
setTimeout(() => {
confettiCanvas.style.display = 'none';
}, 5000);
}

function animateConfetti() {
ctx.clearRect(0, 0, confettiCanvas.width, confettiCanvas.height);
confettiParticles.forEach((p, i) => {
p.y += p.speed;
p.x += Math.sin(p.y / 20) * 2;
p.angle += p.tilt / 100;

ctx.save();
ctx.fillStyle = p.color;
ctx.translate(p.x, p.y);
ctx.rotate(p.angle);
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
ctx.restore();

if (p.y > confettiCanvas.height) {
confettiParticles.splice(i, 1);
}
});

if (confettiParticles.length > 0) {
requestAnimationFrame(animateConfetti);
} else {
confettiCanvas.style.display = 'none';
}
}

// --- App Logic ---
function getBirthdays() {
return JSON.parse(localStorage.getItem('birthdays')) || [];
}

function saveBirthdays(birthdays) {
localStorage.setItem('birthdays', JSON.stringify(birthdays));
}

function calculateDaysLeft(birthdate) {
const today = new Date();
today.setHours(0, 0, 0, 0);

const birthDate = new Date(birthdate);
let nextBirthday = new Date(today.getFullYear(), birthDate.getMonth(), birthDate.getDate());

if (nextBirthday < today) {
nextBirthday.setFullYear(today.getFullYear() + 1);
}

const timeDiff = nextBirthday.getTime() - today.getTime();
return Math.ceil(timeDiff / (1000 * 60 * 60 * 24));
}

function displayBirthdays() {
birthdayList.innerHTML = '';
let birthdays = getBirthdays();

birthdays = birthdays.map(b => ({ ...b, daysLeft: calculateDaysLeft(b.date) }));
birthdays.sort((a, b) => a.daysLeft - b.daysLeft);

if (birthdays.length === 0) {
birthdayList.innerHTML = '<p class="text-center text-gray-500">No birthdays added yet.</p>';
return;
}

let isBirthdayToday = false;

birthdays.forEach(birthday => {
const daysLeft = birthday.daysLeft;
const formattedDate = new Date(birthday.date).toLocaleDateString('en-US', { month: 'long', day: 'numeric' });

let countdownText = '';
let bgColor = 'bg-white';
let textColor = 'text-gray-800';

if (daysLeft === 0) {
countdownText = `<span class="font-bold text-lg text-rose-500">🎉 It's Today! Happy Birthday!</span>`;
bgColor = 'bg-rose-100';
isBirthdayToday = true;
} else if (daysLeft === 1) {
countdownText = `<span class="font-bold">${daysLeft} day</span> left`;
} else {
countdownText = `<span class="font-bold">${daysLeft} days</span> left`;
}

const item = document.createElement('div');
item.className = `birthday-item flex items-center justify-between p-3 ${bgColor} rounded-lg shadow-sm transition-all`;

item.innerHTML = `
<div>
<p class="font-semibold ${textColor}">${birthday.name}</p>
<p class="text-sm text-gray-500">${formattedDate}</p>
</div>
<div class="flex items-center space-x-4">
<p class="text-sm text-gray-600 text-right">${countdownText}</p>
<button data-id="${birthday.id}" class="delete-btn text-red-500 hover:text-red-700">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
</button>
</div>
`;

birthdayList.appendChild(item);
});

if (isBirthdayToday) {
startConfetti();
}
}

addBtn.addEventListener('click', () => {
const name = nameInput.value.trim();
const date = birthdateInput.value;

if (name && date) {
const birthdays = getBirthdays();
const newBirthday = {
id: Date.now(),
name,
date
};
birthdays.push(newBirthday);
saveBirthdays(birthdays);

nameInput.value = '';
birthdateInput.value = '';
displayBirthdays();
} else {
alert('Please enter both name and birthdate.');
}
});

birthdayList.addEventListener('click', (e) => {
const deleteButton = e.target.closest('.delete-btn');
if (deleteButton) {
const idToDelete = parseInt(deleteButton.dataset.id);
let birthdays = getBirthdays();
birthdays = birthdays.filter(b => b.id !== idToDelete);
saveBirthdays(birthdays);
displayBirthdays();
}
});

document.addEventListener('DOMContentLoaded', displayBirthdays);
27 changes: 27 additions & 0 deletions Domains/Frontend/MiniProjects/BirthdayReminder/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
body {
font-family: 'Inter', sans-serif;
overflow: hidden; /* Hide scrollbars when confetti is active */
}

/* Style for the delete button on hover */
.birthday-item:hover .delete-btn {
opacity: 1;
transform: translateX(0);
}

.delete-btn {
opacity: 0;
transform: translateX(10px);
transition: opacity 0.3s ease, transform 0.3s ease;
}

#confetti-canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none; /* Allows clicking through the canvas */
z-index: 1000;
display: none;
}
Loading