chronobirth-magic / script.js
zsoltapp's picture
create me a fancy birth date form, with creazy good itneractivity, very fresh modern design
080e3d8 verified
// ChronoBirth Magic - Interactive Birth Date Form
// ==================== CONFIGURATION ====================
const CONFIG = {
zodiacSigns: [
{ name: 'Capricorn', symbol: 'β™‘', element: 'Earth', start: [12, 22], end: [1, 19] },
{ name: 'Aquarius', symbol: 'β™’', element: 'Air', start: [1, 20], end: [2, 18] },
{ name: 'Pisces', symbol: 'β™“', element: 'Water', start: [2, 19], end: [3, 20] },
{ name: 'Aries', symbol: 'β™ˆ', element: 'Fire', start: [3, 21], end: [4, 19] },
{ name: 'Taurus', symbol: '♉', element: 'Earth', start: [4, 20], end: [5, 20] },
{ name: 'Gemini', symbol: 'β™Š', element: 'Air', start: [5, 21], end: [6, 20] },
{ name: 'Cancer', symbol: 'β™‹', element: 'Water', start: [6, 21], end: [7, 22] },
{ name: 'Leo', symbol: 'β™Œ', element: 'Fire', start: [7, 23], end: [8, 22] },
{ name: 'Virgo', symbol: '♍', element: 'Earth', start: [8, 23], end: [9, 22] },
{ name: 'Libra', symbol: 'β™Ž', element: 'Air', start: [9, 23], end: [10, 22] },
{ name: 'Scorpio', symbol: '♏', element: 'Water', start: [10, 23], end: [11, 21] },
{ name: 'Sagittarius', symbol: '♐', element: 'Fire', start: [11, 22], end: [12, 21] }
],
seasons: [
{ name: 'Spring', start: [3, 20], end: [6, 20], icon: '🌸' },
{ name: 'Summer', start: [6, 21], end: [9, 22], icon: 'β˜€οΈ' },
{ name: 'Autumn', start: [9, 23], end: [12, 20], icon: 'πŸ‚' },
{ name: 'Winter', start: [12, 21], end: [3, 19], icon: '❄️' }
],
dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
};
// ==================== STATE ====================
let currentStep = 1;
const totalSteps = 3;
let birthData = {
day: null,
month: null,
year: null,
time: null,
timezone: 'UTC'
};
// ==================== DOM ELEMENTS ====================
const elements = {
form: document.getElementById('birthForm'),
formCard: document.getElementById('birthFormCard'),
resultsCard: document.getElementById('resultsCard'),
prevBtn: document.getElementById('prevBtn'),
nextBtn: document.getElementById('nextBtn'),
submitBtn: document.getElementById('submitBtn'),
themeToggle: document.getElementById('themeToggle'),
toast: document.getElementById('toast'),
confettiCanvas: document.getElementById('confettiCanvas')
};
// ==================== INITIALIZATION ====================
document.addEventListener('DOMContentLoaded', () => {
initializeEventListeners();
initializeTheme();
updateStepIndicators();
feather.replace();
});
// ==================== EVENT LISTENERS ====================
function initializeEventListeners() {
// Navigation
elements.nextBtn.addEventListener('click', nextStep);
elements.prevBtn.addEventListener('click', prevStep);
elements.form.addEventListener('submit', handleSubmit);
// Theme
elements.themeToggle.addEventListener('click', toggleTheme);
// Preset buttons
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.addEventListener('click', () => applyPreset(btn.dataset.preset));
});
// Input validation and auto-advance
['day', 'month', 'year'].forEach(id => {
const input = document.getElementById(id);
input.addEventListener('input', handleDateInput);
input.addEventListener('blur', validateDate);
});
// Reset
document.getElementById('resetBtn').addEventListener('click', resetForm);
// Share & Save
document.getElementById('shareBtn').addEventListener('click', shareResults);
document.getElementById('saveBtn').addEventListener('click', saveResults);
}
// ==================== THEME ====================
function initializeTheme() {
const savedTheme = localStorage.getItem('chronobirth-theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {
document.documentElement.classList.add('dark');
}
}
function toggleTheme() {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('chronobirth-theme', isDark ? 'dark' : 'light');
// Animate icon
const icon = elements.themeToggle.querySelector('i');
icon.style.transform = 'rotate(360deg)';
setTimeout(() => {
icon.style.transform = '';
feather.replace();
}, 300);
}
// ==================== STEP NAVIGATION ====================
function nextStep() {
if (!validateCurrentStep()) return;
if (currentStep < totalSteps) {
goToStep(currentStep + 1);
}
}
function prevStep() {
if (currentStep > 1) {
goToStep(currentStep - 1);
}
}
function goToStep(step) {
// Update form steps
document.querySelectorAll('.form-step').forEach((el, i) => {
el.classList.remove('active', 'prev');
if (i + 1 === step) {
el.classList.add('active');
} else if (i + 1 < step) {
el.classList.add('prev');
}
});
currentStep = step;
updateStepIndicators();
updateNavigationButtons();
// Update preview on step 3
if (step === 3) {
updateZodiacPreview();
}
}
function updateStepIndicators() {
document.querySelectorAll('.step-indicator').forEach((el, i) => {
el.classList.remove('active', 'completed');
if (i + 1 === currentStep) {
el.classList.add('active');
} else if (i + 1 < currentStep) {
el.classList.add('completed');
el.querySelector('.step-number').innerHTML = '<i data-feather="check" class="w-4 h-4"></i>';
feather.replace();
} else {
el.querySelector('.step-number').textContent = i + 1;
}
});
}
function updateNavigationButtons() {
elements.prevBtn.classList.toggle('hidden', currentStep === 1);
elements.nextBtn.classList.toggle('hidden', currentStep === totalSteps);
elements.submitBtn.classList.toggle('hidden', currentStep !== totalSteps);
}
function validateCurrentStep() {
const currentStepEl = document.querySelector(`.form-step[data-step="${currentStep}"]`);
const inputs = currentStepEl.querySelectorAll('input, select');
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.reportValidity();
isValid = false;
}
});
return isValid;
}
// ==================== INPUT HANDLING ====================
function handleDateInput(e) {
const input = e.target;
const value = input.value;
// Auto-advance for day and month
if (input.id === 'day' && value.length === 2) {
document.getElementById('month').focus();
} else if (input.id === 'year' && value.length === 4) {
document.getElementById('time').focus();
}
// Store data
birthData[input.id] = input.id === 'month' ? parseInt(value) : value;
}
function validateDate() {
const day = parseInt(document.getElementById('day').value);
const month = parseInt(document.getElementById('month').value);
const year = parseInt(document.getElementById('year').value);
if (!day || !month || !year) return;
const date = new Date(year, month - 1, day);
const isValid = date.getDate() === day && date.getMonth() === month - 1;
if (!isValid) {
showToast('Please enter a valid date!', 'error');
}
}
function applyPreset(preset) {
const now = new Date();
const year = now.getFullYear();
const presets = {
today: { day: now.getDate(), month: now.getMonth() + 1, year: year },
newyear: { day: 1, month: 1, year: year },
leap: { day: 29, month: 2, year: 2020 },
halloween: { day: 31, month: 10, year: year }
};
const data = presets[preset];
if (data) {
document.getElementById('day').value = data.day.toString().padStart(2, '0');
document.getElementById('month').value = data.month;
document.getElementById('year').value = data.year;
birthData.day = data.day;
birthData.month = data.month;
birthData.year = data.year;
// Visual feedback
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.style.transform = btn.dataset.preset === preset ? 'scale(1.05)' : '';
btn.style.background = btn.dataset.preset === preset ? '#fef2f2' : '';
});
}
}
// ==================== ZODIAC & CALCULATIONS ====================
function getZodiacSign(day, month) {
for (const sign of CONFIG.zodiacSigns) {
const [startMonth, startDay] = sign.start;
const [endMonth, endDay] = sign.end;
if ((month === startMonth && day >= startDay) ||
(month === endMonth && day <= endDay) ||
(startMonth > endMonth && (month === startMonth && day >= startDay || month === endMonth && day <= endDay))) {
return sign;
}
}
return CONFIG.zodiacSigns[0];
}
function getSeason(day, month) {
for (const season of CONFIG.seasons) {
const [startMonth, startDay] = season.start;
const [endMonth, endDay] = season.end;
const dateNum = month * 100 + day;
const startNum = startMonth * 100 + startDay;
const endNum = endMonth * 100 + endDay;
if (startNum > endNum) {
// Winter spans year boundary
if (dateNum >= startNum || dateNum <= endNum) return season;
} else {
if (dateNum >= startNum && dateNum <= endNum) return season;
}
}
return CONFIG.seasons[3]; // Default to winter
}
function calculateAge(birthDate) {
const now = new Date();
const diff = now - birthDate;
const years = Math.floor(diff / (365.25 * 24 * 60 * 60 * 1000));
const months = Math.floor((diff % (365.25 * 24 * 60 * 60 * 1000)) / (30.44 * 24 * 60 * 60 * 1000));
const days = Math.floor((diff % (30.44 * 24 * 60 * 60 * 1000)) / (24 * 60 * 60 * 1000));
const hours = Math.floor((diff % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000));
return { years, months, days, hours, totalDays: Math.floor(diff / (24 * 60 * 60 * 1000)) };
}
function getNextBirthday(birthDate) {
const now = new Date();
const currentYear = now.getFullYear();
let nextBirthday = new Date(currentYear, birthDate.getMonth(), birthDate.getDate());
if (nextBirthday < now) {
nextBirthday.setFullYear(currentYear + 1);
}
const daysUntil = Math.ceil((nextBirthday - now) / (24 * 60 * 60 * 1000));
return { date: nextBirthday, daysUntil };
}
function updateZodiacPreview() {
const day = parseInt(document.getElementById('day').value);
const month = parseInt(document.getElementById('month').value);
const year = parseInt(document.getElementById('year').value);
if (!day || !month || !year) return;
const zodiac = getZodiacSign(day, month);
const birthDate = new Date(year, month - 1, day);
const dayOfWeek = CONFIG.dayNames[birthDate.getDay()];
const age = calculateAge(birthDate);
// Update preview
document.getElementById('zodiacIcon').textContent = zodiac.symbol;
document.getElementById('zodiacName').textContent = zodiac.name;
document.getElementById('zodiacDates').textContent = `${zodiac.element} Element β€’ ${zodiac.start[0]}/${zodiac.start[1]} - ${zodiac.end[0]}/${zodiac.end[1]}`;
document.getElementById('dayOfWeek').textContent = dayOfWeek;
document.getElementById('agePreview').textContent = `${age.years} years`;
// Animate
const preview = document.getElementById('zodiacPreview');
preview.style.opacity = '1';
preview.style.transform = 'scale(1.02)';
}
// ==================== FORM SUBMISSION ====================
function handleSubmit(e) {
e.preventDefault();
// Gather all data
const day = parseInt(document.getElementById('day').value);
const month = parseInt(document.getElementById('month').value);
const year = parseInt(document.getElementById('year').value);
const time = document.getElementById('time').value;
const timezone = document.getElementById('timezone').value;
const birthDate = new Date(year, month - 1, day);
const zodiac = getZodiacSign(day, month);
const season = getSeason(day, month);
const age = calculateAge(birthDate);
const nextBirthday = getNextBirthday(birthDate);
// Calculate life progress (assuming 80 years)
const lifeProgress = Math.min((age.years / 80) * 100, 100);
// Display results
displayResults({
birthDate,
day, month, year, time, timezone,
zodiac,
season,
age,
nextBirthday,
lifeProgress,
dayOfWeek: CONFIG.dayNames[birthDate.getDay()]
});
}
function displayResults(data) {
// Hide form, show results
elements.formCard.style.display = 'none';
elements.resultsCard.classList.remove('hidden');
// Populate results
document.getElementById('resultZodiacIcon').textContent = data.zodiac.symbol;
document.getElementById('resultDate').textContent = `${data.dayOfWeek}, ${data.month}/${data.day}/${data.year}`;
document.getElementById('resultZodiac').textContent = data.zodiac.name;
document.getElementById('resultZodiacElement').textContent = `${data.zodiac.element} Element`;
document.getElementById('resultAge').textContent = `${data.age.years} years`;
document.getElementById('resultAgeDetailed').textContent = `${data.age.months}m ${data.age.days}d ${data.age.hours}h old`;
document.getElementById('resultDayName').textContent = data.dayOfWeek;
document.getElementById('resultDayType').textContent = getDayType(data.dayOfWeek);
document.getElementById('resultNextBirthday').textContent = data.nextBirthday.date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
document.getElementById('resultDaysUntil').textContent = `${data.nextBirthday.daysUntil} days until party time!`;
document.getElementById('resultSeason').textContent = `${data.season.icon} ${data.season.name}`;
document.getElementById('resultSeasonDates').textContent = getSeasonPeriod(data.season);
// Animate progress bar
setTimeout(() => {
document.getElementById('lifeProgressBar').style.width = `${data.lifeProgress}%`;
}, 300);
document.getElementById('resultLifeProgress').textContent = `${data.lifeProgress.toFixed(1)}% of estimated lifespan`;
// Generate fun facts
generateFunFacts(data);
// Trigger confetti
triggerConfetti();
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
}
function getDayType(day) {
const types = {
'Monday': 'Moon Day πŸŒ™',
'Tuesday': 'Mars Day ♂️',
'Wednesday': 'Mercury Day ☿️',
'Thursday': 'Jupiter Day ♃',
'Friday': 'Venus Day ♀️',
'Saturday': 'Saturn Day β™„',
'Sunday': 'Sun Day β˜‰'
};
return types[day] || 'Special Day';
}
function getSeasonPeriod(season) {
const [startM, startD] = season.start;
const [endM, endD] = season.end;
return `${startM}/${startD} - ${endM}/${endD}`;
}
function generateFunFacts(data) {
const facts = [
`You've been alive for approximately ${data.age.totalDays.toLocaleString()} days`,
`Your heart has beaten about ${(data.age.totalDays * 115000).toLocaleString()} times`,
`You've taken roughly ${(data.age.totalDays * 23000).toLocaleString()} breaths`,
`The moon has orbited Earth ${Math.floor(data.age.years * 12.4)} times since you were born`,
`Your zodiac sign ${data.zodiac.name} is known for being ${getZodiacTrait(data.zodiac.name)}`,
`People born on ${data.dayOfWeek}s are said to be ${getDayTrait(data.dayOfWeek)}`
];
// Add historical events
const historicalEvents = getHistoricalEvents(data.year);
facts.push(...historicalEvents);
const list = document.getElementById('funFactsList');
list.innerHTML = facts.map(fact => `
<li class="flex items-start gap-3 text-slate-700 dark:text-slate-300">
<span class="text-primary-500 mt-1">β€’</span>
<span>${fact}</span>
</li>
`).join('');
}
function getZodiacTrait(sign) {
const traits = {
'Aries': 'bold and ambitious',
'Taurus': 'reliable and patient',
'Gemini': 'adaptable and outgoing',
'Cancer': 'intuitive and sentimental',
'Leo': 'confident and charismatic',
'Virgo': 'analytical and hardworking',
'Libra': 'diplomatic and gracious',
'Scorpio': 'passionate and resourceful',
'Sagittarius': 'generous and idealistic',
'Capricorn': 'responsible and disciplined',
'Aquarius': 'progressive and original',
'Pisces': 'compassionate and artistic'
};
return traits[sign] || 'unique and special';
}
function getDayTrait(day) {
const traits = {
'Monday': 'creative and intuitive',
'Tuesday': 'energetic and courageous',
'Wednesday': 'communicative and curious',
'Thursday': 'expansive and optimistic',
'Friday': 'loving and harmonious',
'Saturday': 'wise and disciplined',
'Sunday': 'vital and radiant'
};
return traits[day] || 'wonderful';
}
function getHistoricalEvents(year) {
const events = {
2024: ['AI continues to transform technology', 'Space exploration reaches new heights'],
2023: ['ChatGPT became widely popular', 'James Webb Space Telescope discoveries'],
2020: ['COVID-19 pandemic changed the world', 'SpaceX launched first crewed mission'],
2000: ['Y2K bug was avoided', 'Human genome project completed'],
1990: ['World Wide Web was invented', 'Hubble Space Telescope launched'],
1980: ['Pac-Man was released', 'CNN launched as first 24-hour news channel'],
1970: ['First Earth Day celebrated', 'Floppy disk invented'],
1969: ['Humans first landed on the Moon', 'Internet precursor ARPANET launched'],
1960: ['Laser was invented', 'First weather satellite launched']
};
// Find closest year with events
const years = Object.keys(events).map(Number).sort((a, b) => b - a);
const closestYear = years.find(y => y <= year) || years[years.length - 1];
return events[closestYear] ? events[closestYear].map(e => `In ${closestYear}: ${e}`) : [];
}
// ==================== ACTIONS ====================
function resetForm() {
elements.form.reset();
currentStep = 1;
birthData = { day: null, month: null, year: null, time: null, timezone: 'UTC' };
elements.resultsCard.classList.add('hidden');
elements.formCard.style.display = 'block';
document.getElementById('lifeProgressBar').style.width = '0%';
goToStep(1);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
async function shareResults() {
const text = `I discovered my cosmic birthday profile with ChronoBirth! πŸŽ‚βœ¨`;
try {
if (navigator.share) {
await navigator.share({ title: 'My ChronoBirth Profile', text });
} else {
await navigator.clipboard.writeText(text + ' https://chronobirth.magic');
showToast('Link copied to clipboard!');
}
} catch (err) {
showToast('Unable to share', 'error');
}
}
function saveResults() {
// Create a simple text representation
const results = document.getElementById('resultsCard').innerText;
const blob = new Blob([results], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'my-chronobirth-profile.txt';
a.click();
URL.revokeObjectURL(url);
showToast('Profile saved!');
}
function showToast(message, type = 'success') {
const toast = elements.toast;
const toastMessage = document.getElementById('toastMessage');
toastMessage.textContent = message;
toast.querySelector('i').className = type === 'error'
? 'w-5 h-5 text-rose-500'
: 'w-5 h-5 text-emerald-400 dark:text-emerald-500';
toast.style.transform = 'translateX(-50%) translateY(0)';
toast.style.opacity = '1';
setTimeout(() => {
toast.style.transform = 'translateX(-50%) translateY(20px)';
toast.style.opacity = '0';
}, 3000);
}
// ==================== CONFETTI ====================
function triggerConfetti() {
const canvas = elements.confettiCanvas;
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const particles = [];
const colors = ['#ef4444', '#f59e0b', '#10b981', '#8b5cf6', '#ec4899', '#06b6d4'];
// Create particles
for (let i = 0; i < 150; i++) {
particles.push({
x: canvas.width / 2,
y: canvas.height / 2,
vx: (Math.random() - 0.5) * 15,
vy: (Math.random() - 0.5) * 15 - 5,
color: colors[Math.floor(Math.random() * colors.length)],
size: Math.random() * 8 + 4,
rotation: Math.random() * 360,
rotationSpeed: (Math.random() - 0.5) * 10,
gravity: 0.3,
drag: 0.98
});
}
let animationId;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
let activeParticles = 0;
particles.forEach(p => {
if (p.y < canvas.height + 50) {
activeParticles++;
p.x += p.vx;
p.y += p.vy;
p.vy += p.gravity;
p.vx *= p.drag;
p.vy *= p.drag;
p.rotation += p.rotationSpeed;
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate(p.rotation * Math.PI / 180);
ctx.fillStyle = p.color;
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
ctx.restore();
}
});
if (activeParticles > 0) {
animationId = requestAnimationFrame(animate);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
animate();
// Cleanup on resize
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}, { once: true });
}
// ==================== UTILITY ====================
// Add smooth scroll behavior
document.documentElement.style.scrollBehavior = 'smooth';
// Prevent form submission on Enter for multi-step
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && currentStep < totalSteps) {
e.preventDefault();
nextStep();
}
});