import React, { useState, useEffect, useRef, useCallback } from 'react';
// Main App component for the Star Map Generator
const App = () => {
// State variables for user inputs
const [date, setDate] = useState('2022-08-22');
const [time, setTime] = useState('20:00');
const [location, setLocation] = useState('Suryapet, India');
const [mainName, setMainName] = useState('Welcome To The World My Dear Baby');
const [subMessage, setSubMessage] = useState('');
const [coordinates, setCoordinates] = useState('17.135009, 79.628791');
const [message, setMessage] = useState(''); // For user feedback messages
// Ref for the canvas element
const canvasRef = useRef(null);
// Ref for the entire output div to capture for download
const outputDivRef = useRef(null);
// Simulated Constellation Data (Illustrative, NOT Astronomically Accurate)
// In a real accurate tool, this data would come from your backend API
// after performing astronomical calculations.
const simulatedConstellations = [
// Constellation 1: A Big Dipper-like pattern
{
stars: [
{ x: 0.2, y: 0.1 }, { x: 0.3, y: 0.15 }, { x: 0.4, y: 0.2 },
{ x: 0.5, y: 0.25 }, { x: 0.6, y: 0.3 }, { x: 0.7, y: 0.35 },
{ x: 0.8, y: 0.4 }
],
lines: [
[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6]
]
},
// Constellation 2: An Orion-like pattern
{
stars: [
{ x: 0.7, y: 0.1 }, { x: 0.8, y: 0.2 }, { x: 0.6, y: 0.25 },
{ x: 0.75, y: 0.35 }, { x: 0.65, y: 0.45 }, { x: 0.85, y: 0.4 }
],
lines: [
[0, 1], [1, 2], [2, 0], // Triangle
[1, 3], [3, 4], [4, 5] // Belt and sword
]
},
// Constellation 3: A 'W' or 'M' shape (Cassiopeia-like)
{
stars: [
{ x: 0.1, y: 0.7 }, { x: 0.25, y: 0.6 }, { x: 0.4, y: 0.75 },
{ x: 0.55, y: 0.65 }, { x: 0.7, y: 0.8 }
],
lines: [
[0, 1], [1, 2], [2, 3], [3, 4]
]
},
// Constellation 4: A Cross shape (Cygnus-like)
{
stars: [
{ x: 0.5, y: 0.05 }, { x: 0.5, y: 0.25 }, { x: 0.4, y: 0.15 },
{ x: 0.6, y: 0.15 }, { x: 0.5, y: 0.35 }
],
lines: [
[0, 1], [1, 4], [2, 1], [3, 1]
]
},
// Add more complex, interconnected constellations for better visual appeal
// Constellation 5: More scattered, interconnected
{
stars: [
{ x: 0.15, y: 0.4 }, { x: 0.25, y: 0.5 }, { x: 0.1, y: 0.55 },
{ x: 0.3, y: 0.6 }, { x: 0.4, y: 0.5 }, { x: 0.35, y: 0.4 }
],
lines: [
[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0], [0, 3]
]
},
// Constellation 6: Another complex pattern
{
stars: [
{ x: 0.8, y: 0.6 }, { x: 0.7, y: 0.7 }, { x: 0.9, y: 0.75 },
{ x: 0.75, y: 0.85 }, { x: 0.85, y: 0.9 }
],
lines: [
[0, 1], [1, 3], [3, 4], [4, 2], [2, 0]
]
},
// Constellation 7: Small cluster
{
stars: [
{ x: 0.45, y: 0.1 }, { x: 0.55, y: 0.05 }, { x: 0.5, y: 0.2 }
],
lines: [
[0, 1], [1, 2], [2, 0]
]
},
// Constellation 8: Linear pattern
{
stars: [
{ x: 0.1, y: 0.2 }, { x: 0.05, y: 0.3 }, { x: 0.15, y: 0.35 }, { x: 0.2, y: 0.45 }
],
lines: [
[0, 1], [1, 2], [2, 3]
]
}
];
// Function to display feedback messages to the user
const showMessage = useCallback((msg, type = 'info') => {
setMessage(msg);
// You can add styling classes based on type (e.g., 'text-red-500' for error)
// For simplicity, we're just setting the text here.
// In a real app, you'd manage a state for message type and apply Tailwind classes.
const msgBox = document.getElementById('messageBox');
if (msgBox) {
msgBox.textContent = msg;
msgBox.className = `message-box block mt-4 p-3 rounded-lg text-center ${
type === 'error' ? 'bg-red-600' : type === 'success' ? 'bg-green-600' : 'bg-blue-600'
}`;
msgBox.style.display = 'block';
setTimeout(() => {
msgBox.style.display = 'none';
setMessage('');
}, 5000); // Hide after 5 seconds
}
}, []);
// Function to draw the star map on the canvas
const drawStarMap = useCallback(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
const containerSize = canvas.parentElement.clientWidth;
canvas.width = containerSize;
canvas.height = containerSize;
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear canvas
ctx.fillStyle = '#000000'; // Black background for the circular map area
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw a circular clipping path for the stars
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(canvas.width, canvas.height) / 2 - 5; // Slightly smaller than half to leave a tiny border
ctx.save(); // Save the current canvas state
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
ctx.clip(); // Clip everything outside this circle
// Simulate drawing background stars (more dense and varied)
for (let i = 0; i < 3500; i++) { // Increased star count for higher density
const angle = Math.random() * Math.PI * 2;
const dist = Math.random() * radius;
const x = centerX + dist * Math.cos(angle);
const y = centerY + dist * Math.sin(angle);
const starRadius = Math.random() * 1.5 + 0.3; // Smaller, more varied star sizes
const opacity = Math.random() * 0.9 + 0.05; // More varied brightness, some very faint
ctx.beginPath();
ctx.arc(x, y, starRadius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 255, 255, ${opacity})`;
ctx.shadowBlur = starRadius * 2; // Subtle glow
ctx.shadowColor = `rgba(255, 255, 255, ${opacity * 0.5})`;
ctx.fill();
}
ctx.restore(); // Restore the canvas state to remove the clipping path
// Draw the celestial grid and cardinal directions
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; // Grid lines
ctx.lineWidth = 0.5;
ctx.font = '10px Inter';
ctx.fillStyle = '#FFFFFF';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Outer circle for the compass/degree markings
ctx.beginPath();
ctx.arc(centerX, centerY, radius + 10, 0, Math.PI * 2); // Slightly larger circle
ctx.stroke();
// Degree markings and cardinal directions
const outerRadius = radius + 15; // Radius for text placement
const innerRadius = radius + 5; // Radius for tick marks
for (let i = 0; i < 360; i += 10) { // Every 10 degrees
const angleRad = (i - 90) * Math.PI / 180; // Adjust for canvas 0deg at right
// Draw tick marks
ctx.beginPath();
ctx.moveTo(centerX + innerRadius * Math.cos(angleRad), centerY + innerRadius * Math.sin(angleRad));
ctx.lineTo(centerX + (innerRadius + 5) * Math.cos(angleRad), centerY + (innerRadius + 5) * Math.sin(angleRad));
ctx.stroke();
// Draw degree numbers
if (i % 30 === 0) { // Every 30 degrees
ctx.fillText(i.toString(), centerX + outerRadius * Math.cos(angleRad), centerY + outerRadius * Math.sin(angleRad));
}
}
// Cardinal directions
ctx.font = '14px Inter';
ctx.fillText('N', centerX, centerY - outerRadius - 10);
ctx.fillText('S', centerX, centerY + outerRadius + 10);
ctx.fillText('E', centerX + outerRadius + 10, centerY);
ctx.fillText('W', centerX - outerRadius - 10, centerY);
// Draw simulated constellations
ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)'; // More visible lines
ctx.lineWidth = 1;
ctx.fillStyle = 'rgba(255, 255, 255, 1)'; // Brighter constellation star color
simulatedConstellations.forEach(constellation => {
// Scale and offset constellation points to fit within the circle and distribute them
const constellationScale = radius * 0.8; // Scale to fill most of the circle
const constellationOffsetX = centerX - constellationScale / 2;
const constellationOffsetY = centerY - constellationScale / 2;
const transformedStars = constellation.stars.map(star => ({
x: star.x * constellationScale + constellationOffsetX,
y: star.y * constellationScale + constellationOffsetY
}));
// Draw lines
ctx.beginPath();
constellation.lines.forEach(line => {
ctx.moveTo(transformedStars[line[0]].x, transformedStars[line[0]].y);
ctx.lineTo(transformedStars[line[1]].x, transformedStars[line[1]].y);
});
ctx.stroke();
// Draw stars (dots)
transformedStars.forEach(star => {
ctx.beginPath();
ctx.arc(star.x, star.y, 2.5, 0, Math.PI * 2); // Larger, more prominent constellation stars
ctx.shadowBlur = 5; // More prominent glow
ctx.shadowColor = `rgba(255, 255, 255, 0.8)`;
ctx.fill();
});
});
showMessage("Star map generated successfully!", "success");
}, []); // Dependencies for useCallback
// Handle map generation button click
const handleGenerateMap = () => {
// --- IMPORTANT: This is where you would call your backend API for accurate data ---
// Example of what an API call might look like (conceptual):
/*
fetch('/api/generate-starmap', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
date,
time,
location,
coordinates, // If you want to send coordinates for more precision
}),
})
.then(response => response.json())
.then(data => {
// Assuming your API returns an image URL or base64 data
// You would then load this image onto the canvas or display it
// For now, we're just calling the simulated draw function
drawStarMap();
showMessage("Accurate star map generated!", "success");
})
.catch(error => {
console.error('Error generating star map:', error);
showMessage("Error generating star map. Please try again.", "error");
});
*/
// For this demo, we just redraw the simulated map
drawStarMap();
};
// Handle map download button click
const handleDownloadMap = () => {
// To download the entire output div as an image (including text),
// you would typically use a library like 'html2canvas'.
// This library is not available in this environment, so we'll
// provide a conceptual example and fall back to downloading just the canvas.
// Conceptual html2canvas usage:
/*
if (window.html2canvas && outputDivRef.current) {
html2canvas(outputDivRef.current, {
backgroundColor: '#1a202c', // Match the background of the output div
scale: 2 // Increase scale for higher resolution output
}).then(canvas => {
const link = document.createElement('a');
link.download = 'personalized_star_map.png';
link.href = canvas.toDataURL('image/png');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showMessage("Full star map downloaded!", "info");
});
} else {
// Fallback to downloading just the canvas if html2canvas is not available
const canvas = canvasRef.current;
if (canvas) {
const dataURL = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = 'personalized_star_map.png';
link.href = dataURL;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showMessage("Star map (circular part) downloaded!", "info");
} else {
showMessage("No map to download. Generate one first!", "error");
}
}
*/
// For this demo, we download just the canvas
const canvas = canvasRef.current;
if (canvas) {
const dataURL = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = 'personalized_star_map.png';
link.href = dataURL;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
showMessage("Star map (circular part) downloaded!", "info");
} else {
showMessage("No map to download. Generate one first!", "error");
}
};
// Effect to draw the map initially and on resize
useEffect(() => {
drawStarMap();
const resizeListener = () => {
const outputDiv = outputDivRef.current;
if (outputDiv) {
const containerWidth = outputDiv.parentElement.clientWidth;
outputDiv.style.maxWidth = `${containerWidth > 600 ? 600 : containerWidth}px`;
}
drawStarMap(); // Redraw map on resize
};
window.addEventListener('resize', resizeListener);
return () => window.removeEventListener('resize', resizeListener);
}, [drawStarMap]); // Redraw when drawStarMap function changes (which it won't in this case, but good practice)
// Format date for display
const formatDate = (dateString) => {
try {
const dateObj = new Date(dateString);
return `${dateObj.getMonth() + 1}/${dateObj.getDate()}/${dateObj.getFullYear().toString().slice(-2)}`;
} catch (e) {
return dateString; // Return as is if invalid
}
};
return (
<div className="flex justify-center items-center min-h-screen p-5 box-border bg-gray-900 text-gray-100">
<div className="bg-gray-700 rounded-2xl shadow-xl p-8 max-w-4xl w-full flex flex-col gap-6">
<h1 className="text-4xl font-bold text-center text-white mb-4">Personal Star Map Builder</h1>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="input-group">
<label htmlFor="dateInput" className="block mb-2 font-semibold text-gray-300">Date:</label>
<input
type="date"
id="dateInput"
className="w-full p-3 rounded-lg border border-gray-600 bg-gray-800 text-gray-100 focus:border-blue-400 outline-none transition-colors"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
</div>
<div className="input-group">
<label htmlFor="timeInput" className="block mb-2 font-semibold text-gray-300">Time (24-hour):</label>
<input
type="time"
id="timeInput"
className="w-full p-3 rounded-lg border border-gray-600 bg-gray-800 text-gray-100 focus:border-blue-400 outline-none transition-colors"
value={time}
onChange={(e) => setTime(e.target.value)}
/>
</div>
<div className="input-group md:col-span-2">
<label htmlFor="locationInput" className="block mb-2 font-semibold text-gray-300">Location (City, Country):</label>
<input
type="text"
id="locationInput"
className="w-full p-3 rounded-lg border border-gray-600 bg-gray-800 text-gray-100 focus:border-blue-400 outline-none transition-colors"
value={location}
onChange={(e) => setLocation(e.target.value)}
placeholder="E.g., Hyderabad, India"
/>
</div>
<div className="input-group md:col-span-2">
<label htmlFor="mainNameInput" className="block mb-2 font-semibold text-gray-300">Main Name/Title:</label>
<input
type="text"
id="mainNameInput"
className="w-full p-3 rounded-lg border border-gray-600 bg-gray-800 text-gray-100 focus:border-blue-400 outline-none transition-colors"
value={mainName}
onChange={(e) => setMainName(e.target.value)}
placeholder="E.g., 'Gnananavika' or 'Our Special Day'"
/>
</div>
<div className="input-group md:col-span-2">
<label htmlFor="subMessageInput" className="block mb-2 font-semibold text-gray-300">Sub-Message (e.g., 'The night sky the day you were born'):</label>
<textarea
id="subMessageInput"
rows="2"
className="w-full p-3 rounded-lg border border-gray-600 bg-gray-800 text-gray-100 focus:border-blue-400 outline-none transition-colors"
value={subMessage}
onChange={(e) => setSubMessage(e.target.value)}
placeholder="E.g., 'The night sky the day you were born'"
></textarea>
</div>
<div className="input-group md:col-span-2">
<label htmlFor="coordinatesInput" className="block mb-2 font-semibold text-gray-300">Coordinates (Optional, e.g., 17.140417° N,79.62942° E):</label>
<input
type="text"
id="coordinatesInput"
className="w-full p-3 rounded-lg border border-gray-600 bg-gray-800 text-gray-100 focus:border-blue-400 outline-none transition-colors"
value={coordinates}
onChange={(e) => setCoordinates(e.target.value)}
placeholder="E.g., 17.140417° N,79.62942° E"
/>
</div>
</div>
<button
onClick={handleGenerateMap}
className="btn-primary w-full py-3 rounded-lg font-bold text-lg bg-blue-500 hover:bg-blue-600 transition-all duration-200 shadow-md hover:shadow-lg"
>
Generate Star Map
</button>
<div id="messageBox" className="message-box hidden"></div>
{/* Output div to hold the generated canvas image and text */}
<div ref={outputDivRef} className="star-map-output mt-6 bg-gray-900 border-2 border-gray-100 rounded-lg p-5 flex flex-col items-center justify-between mx-auto">
<div className="star-map-circle-container w-[90%] pt-[90%] relative rounded-full overflow-hidden bg-black border border-gray-700 mt-5">
<canvas ref={canvasRef} className="star-map-circle-canvas absolute top-0 left-0 w-full h-full"></canvas>
</div>
<div className="text-section w-full py-5 text-center">
<h2 className="font-montserrat font-bold text-4xl text-gray-100 mb-2 leading-tight">{mainName}</h2>
<div className="w-12 h-0.5 bg-gray-400 mx-auto my-4"></div> {/* Divider */}
<p className="font-inter text-sm text-gray-400 leading-snug">{subMessage}</p>
<p className="font-inter text-sm text-gray-400 leading-snug">{formatDate(date)}</p>
<p className="font-inter text-sm text-gray-400 leading-snug">{coordinates}</p>
</div>
</div>
<button
onClick={handleDownloadMap}
className="btn-secondary w-full py-3 rounded-lg font-bold text-lg bg-gray-600 hover:bg-gray-800 transition-all duration-200 shadow-md hover:shadow-lg border border-blue-400 text-gray-100"
>
Download Star Map
</button>
</div>
</div>
);
};
export default App;