diff --git a/Domains/Frontend/MiniProjects/BirthdayReminder/README.md b/Domains/Frontend/MiniProjects/BirthdayReminder/README.md new file mode 100644 index 00000000..34c2c3de --- /dev/null +++ b/Domains/Frontend/MiniProjects/BirthdayReminder/README.md @@ -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. + +--- \ No newline at end of file diff --git a/Domains/Frontend/MiniProjects/BirthdayReminder/index.html b/Domains/Frontend/MiniProjects/BirthdayReminder/index.html new file mode 100644 index 00000000..1bb5fd52 --- /dev/null +++ b/Domains/Frontend/MiniProjects/BirthdayReminder/index.html @@ -0,0 +1,49 @@ + + + + + + Birthday Reminder + + + + + + + + + + +
+
+

Birthday Countdown

+

Never miss a special day again!

+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+

Upcoming Birthdays

+
+ +
+
+
+ + + + diff --git a/Domains/Frontend/MiniProjects/BirthdayReminder/script.js b/Domains/Frontend/MiniProjects/BirthdayReminder/script.js new file mode 100644 index 00000000..43e3d8b8 --- /dev/null +++ b/Domains/Frontend/MiniProjects/BirthdayReminder/script.js @@ -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 = '

No birthdays added yet.

'; + 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 = `๐ŸŽ‰ It's Today! Happy Birthday!`; + bgColor = 'bg-rose-100'; + isBirthdayToday = true; + } else if (daysLeft === 1) { + countdownText = `${daysLeft} day left`; + } else { + countdownText = `${daysLeft} days 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 = ` +
+

${birthday.name}

+

${formattedDate}

+
+
+

${countdownText}

+ +
+ `; + + 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); diff --git a/Domains/Frontend/MiniProjects/BirthdayReminder/style.css b/Domains/Frontend/MiniProjects/BirthdayReminder/style.css new file mode 100644 index 00000000..ebdc5747 --- /dev/null +++ b/Domains/Frontend/MiniProjects/BirthdayReminder/style.css @@ -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; +}