Spaces:
Running
Running
| import React, { useState, useEffect, useRef } from "react"; | |
| import { | |
| fetchHoroscopeData, | |
| fallbackHoroscopeContent, | |
| } from "./services/horoscopeService"; | |
| // Zodiac Icon Components using Tabler Icons SVGs | |
| const ZodiacIcon = ({ sign, size = 24, className = "" }) => { | |
| const iconPaths = { | |
| taurus: [ | |
| "M6 3a6 6 0 0 0 12 0", | |
| "M12 15m-6 0a6 6 0 1 0 12 0a6 6 0 1 0 -12 0", | |
| ], | |
| sagittarius: ["M4 20l16 -16", "M13 4h7v7", "M6.5 12.5l5 5"], | |
| pisces: ["M5 3a21 21 0 0 1 0 18", "M19 3a21 21 0 0 0 0 18", "M5 12l14 0"], | |
| libra: ["M5 20l14 0", "M5 17h5v-.3a7 7 0 1 1 4 0v.3h5"], | |
| gemini: [ | |
| "M3 3a21 21 0 0 0 18 0", | |
| "M3 21a21 21 0 0 1 18 0", | |
| "M7 4.5l0 15", | |
| "M17 4.5l0 15", | |
| ], | |
| leo: [ | |
| "M13 17a4 4 0 1 0 8 0", | |
| "M6 16m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0", | |
| "M11 7m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0", | |
| "M7 7c0 3 2 5 2 9", | |
| "M15 7c0 4 -2 6 -2 10", | |
| ], | |
| virgo: [ | |
| "M3 4a2 2 0 0 1 2 2v9", | |
| "M5 6a2 2 0 0 1 4 0v9", | |
| "M9 6a2 2 0 0 1 4 0v10a7 5 0 0 0 7 5", | |
| "M12 21a7 5 0 0 0 7 -5v-2a3 3 0 0 0 -6 0", | |
| ], | |
| aquarius: [ | |
| "M3 10l3 -3l3 3l3 -3l3 3l3 -3l3 3", | |
| "M3 17l3 -3l3 3l3 -3l3 3l3 -3l3 3", | |
| ], | |
| aries: ["M12 5a5 5 0 1 0 -4 8", "M16 13a5 5 0 1 0 -4 -8", "M12 21l0 -16"], | |
| cancer: [ | |
| "M6 12m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0", | |
| "M18 12m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0", | |
| "M3 12a10 6.5 0 0 1 14 -6.5", | |
| "M21 12a10 6.5 0 0 1 -14 6.5", | |
| ], | |
| scorpio: [ | |
| "M3 4a2 2 0 0 1 2 2v9", | |
| "M5 6a2 2 0 0 1 4 0v9", | |
| "M9 6a2 2 0 0 1 4 0v10a3 3 0 0 0 3 3h5l-3 -3m0 6l3 -3", | |
| ], | |
| capricorn: [ | |
| "M4 4a3 3 0 0 1 3 3v9", | |
| "M7 7a3 3 0 0 1 6 0v11a3 3 0 0 1 -3 3", | |
| "M16 17m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0", | |
| ], | |
| }; | |
| const paths = iconPaths[sign] || []; | |
| return ( | |
| <svg | |
| width={size} | |
| height={size} | |
| viewBox="0 0 24 24" | |
| fill="none" | |
| stroke="currentColor" | |
| strokeWidth="2" | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| className={className} | |
| > | |
| <path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |
| {paths.map((path, index) => ( | |
| <path key={index} d={path} /> | |
| ))} | |
| </svg> | |
| ); | |
| }; | |
| const HoroscopeApp = () => { | |
| const [selectedSign, setSelectedSign] = useState("virgo"); | |
| const [selectedGender, setSelectedGender] = useState("female"); | |
| const [selectedPeriod, setSelectedPeriod] = useState("today"); | |
| const [selectedCategory, setSelectedCategory] = useState("overall"); | |
| const [isDarkMode, setIsDarkMode] = useState(false); | |
| const [horoscopeData, setHoroscopeData] = useState(null); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [error, setError] = useState(null); | |
| const zodiacScrollRef = useRef(null); | |
| const loadHoroscopeData = async (category, period) => { | |
| setIsLoading(true); | |
| setError(null); | |
| try { | |
| const data = await fetchHoroscopeData(category, period); | |
| if (data && data.data) { | |
| setHoroscopeData(data); | |
| } else { | |
| setHoroscopeData(null); | |
| setError("No data available from GitHub, using fallback content"); | |
| } | |
| } catch (err) { | |
| setError("Failed to load horoscope data"); | |
| setHoroscopeData(null); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }; | |
| const zodiacSigns = [ | |
| { | |
| id: "aries", | |
| name: "Aries", | |
| dates: "Mar 21 - Apr 19", | |
| emoji: "π", | |
| symbol: "β", | |
| imageFile: "ARIES.png", | |
| }, | |
| { | |
| id: "taurus", | |
| name: "Taurus", | |
| dates: "April 20 - May 20", | |
| emoji: "π", | |
| symbol: "β", | |
| imageFile: "TAURUS.png", | |
| }, | |
| { | |
| id: "gemini", | |
| name: "Gemini", | |
| dates: "May 21 - Jun 20", | |
| emoji: "π―", | |
| symbol: "β", | |
| imageFile: "GEMINI.png", | |
| }, | |
| { | |
| id: "cancer", | |
| name: "Cancer", | |
| dates: "Jun 21 - Jul 22", | |
| emoji: "π¦", | |
| symbol: "β", | |
| imageFile: "CANCER.png", | |
| }, | |
| { | |
| id: "leo", | |
| name: "Leo", | |
| dates: "Jul 23 - Aug 22", | |
| emoji: "π¦", | |
| symbol: "β", | |
| imageFile: "LEO.png", | |
| }, | |
| { | |
| id: "virgo", | |
| name: "Virgo", | |
| dates: "Aug 23 - Sep 22", | |
| emoji: "πΈ", | |
| symbol: "β", | |
| imageFile: "VIRGO.png", | |
| }, | |
| { | |
| id: "libra", | |
| name: "Libra", | |
| dates: "Sep 23 - Oct 22", | |
| emoji: "βοΈ", | |
| symbol: "β", | |
| imageFile: "LIBRA.png", | |
| }, | |
| { | |
| id: "scorpio", | |
| name: "Scorpio", | |
| dates: "Oct 23 - Nov 21", | |
| emoji: "π¦", | |
| symbol: "β", | |
| imageFile: "SCORPIO.png", | |
| }, | |
| { | |
| id: "sagittarius", | |
| name: "Sagittarius", | |
| dates: "Nov 22 - Dec 21", | |
| emoji: "πΉ", | |
| symbol: "β", | |
| imageFile: "SAGITTARIUS.png", | |
| }, | |
| { | |
| id: "capricorn", | |
| name: "Capricorn", | |
| dates: "Dec 22 - Jan 19", | |
| emoji: "π", | |
| symbol: "β", | |
| imageFile: "CAPRICORN.png", | |
| }, | |
| { | |
| id: "aquarius", | |
| name: "Aquarius", | |
| dates: "Jan 20 - Feb 18", | |
| emoji: "πΊ", | |
| symbol: "β", | |
| imageFile: "AQUARIUS.png", | |
| }, | |
| { | |
| id: "pisces", | |
| name: "Pisces", | |
| dates: "Feb 19 - Mar 20", | |
| emoji: "π ", | |
| symbol: "β", | |
| imageFile: "PISCES.png", | |
| }, | |
| ]; | |
| const periods = [ | |
| { id: "today", name: "Today" }, | |
| { id: "week", name: "Week" }, | |
| { id: "month", name: "Month" }, | |
| { id: "year", name: "Year" }, | |
| ]; | |
| const categories = [ | |
| { | |
| id: "overall", | |
| name: "Overall", | |
| icon: <i className="ri-dashboard-line"></i>, | |
| }, | |
| { | |
| id: "fortune", | |
| name: "Fortune", | |
| icon: <i className="ri-seedling-line"></i>, | |
| }, | |
| { | |
| id: "health", | |
| name: "Health", | |
| icon: <i className="ri-open-arm-line"></i>, | |
| }, | |
| { id: "love", name: "Love", icon: <i className="ri-hearts-line"></i> }, | |
| { | |
| id: "finance", | |
| name: "Finance", | |
| icon: <i className="ri-wallet-3-line"></i>, | |
| }, | |
| { | |
| id: "relationship", | |
| name: "Relationship", | |
| icon: <i className="ri-team-line"></i>, | |
| }, | |
| ]; | |
| // const horoscopeContent = { | |
| // overall: { | |
| // today: | |
| // "Today brings a harmonious blend of opportunities and challenges across all aspects of your life. Your natural instincts will guide you well.", | |
| // week: "This week promises balanced growth in multiple areas. Trust your intuition as you navigate new experiences.", | |
| // month: | |
| // "A month of significant personal development awaits. Embrace change and new perspectives with confidence.", | |
| // year: "This year marks a transformative period of growth, bringing profound insights and meaningful achievements.", | |
| // }, | |
| // love: { | |
| // today: | |
| // "Love is in the air today! Your romantic side is highlighted, making it a perfect day for expressing your feelings.", | |
| // week: "This week brings romantic opportunities and deepening connections with your loved ones.", | |
| // month: | |
| // "A month of passionate encounters and meaningful relationships awaits you.", | |
| // year: "This year will be transformative for your love life, bringing lasting happiness.", | |
| // }, | |
| // health: { | |
| // today: | |
| // "Your energy levels are high today. Focus on maintaining balance between work and rest.", | |
| // week: "Pay attention to your physical wellbeing this week. Small changes can make big differences.", | |
| // month: | |
| // "This month emphasizes the importance of mental and physical health harmony.", | |
| // year: "A year of wellness and vitality lies ahead. Make health your priority.", | |
| // }, | |
| // relationship: { | |
| // today: | |
| // "Relationships flourish under today's cosmic energy. Communication is key to success.", | |
| // week: "Strengthen your bonds with family and friends through meaningful conversations.", | |
| // month: | |
| // "Social connections expand this month, bringing new friendships and opportunities.", | |
| // year: "Your social circle will grow significantly, enriching your life in unexpected ways.", | |
| // }, | |
| // finance: { | |
| // today: | |
| // "Financial opportunities present themselves today. Stay alert for new possibilities.", | |
| // week: "This week favors careful financial planning and smart investments.", | |
| // month: | |
| // "Money matters require attention this month. Budget wisely for future security.", | |
| // year: "Financial growth and stability are highlighted throughout this year.", | |
| // }, | |
| // fortune: { | |
| // today: | |
| // "Lucky energies surround you today. Trust your instincts and take calculated risks for unexpected rewards.", | |
| // week: "Fortune favors the bold this week. Positive surprises and serendipitous encounters await.", | |
| // month: | |
| // "A month of abundance and good fortune unfolds. Opportunities align perfectly with your goals.", | |
| // year: "This year brings remarkable luck and prosperity. Your efforts will be rewarded beyond expectations.", | |
| // }, | |
| // }; | |
| // Function to get image path based on gender | |
| const getImagePath = (sign) => { | |
| if (!sign.imageFile) return null; | |
| return `/img/${selectedGender}/${sign.imageFile}`; | |
| }; | |
| const getCurrentSign = () => { | |
| return zodiacSigns.find((sign) => sign.id === selectedSign); | |
| }; | |
| const getHoroscopeText = () => { | |
| // Hiα»n thα» loading state | |
| if (isLoading) { | |
| return "Loading your horoscope..."; | |
| } | |
| if (!horoscopeData || error) { | |
| if (selectedPeriod !== "today") { | |
| return "Upgrade to Premium to unlock weekly, monthly, and yearly horoscope insights. Get deeper cosmic guidance and detailed predictions for your future."; | |
| } | |
| return fallbackHoroscopeContent[selectedCategory][selectedPeriod]; | |
| } | |
| const currentSign = getCurrentSign(); | |
| if (currentSign && horoscopeData.data[currentSign.id]) { | |
| const signData = horoscopeData.data[currentSign.id]; | |
| return ( | |
| signData[selectedGender] || | |
| signData.male || | |
| signData.female || | |
| "No horoscope available for today." | |
| ); | |
| } | |
| return "No horoscope available for this sign."; | |
| }; | |
| const getPeriodTitle = () => { | |
| const titles = { | |
| today: "Horoscope for Today", | |
| week: "Horoscope for This Week", | |
| month: "Horoscope for This Month", | |
| year: "Horoscope for This Year", | |
| }; | |
| return titles[selectedPeriod]; | |
| }; | |
| const toggleTheme = () => { | |
| setIsDarkMode(!isDarkMode); | |
| }; | |
| // Scroll to selected zodiac sign on mount and selection change | |
| useEffect(() => { | |
| if (zodiacScrollRef.current) { | |
| const selectedElement = zodiacScrollRef.current.querySelector( | |
| `[data-sign="${selectedSign}"]` | |
| ); | |
| if (selectedElement) { | |
| selectedElement.scrollIntoView({ | |
| behavior: "smooth", | |
| block: "nearest", | |
| inline: "center", | |
| }); | |
| } | |
| } | |
| }, [selectedSign]); | |
| // Load horoscope data when category or period changes | |
| useEffect(() => { | |
| loadHoroscopeData(selectedCategory, selectedPeriod); | |
| }, [selectedCategory, selectedPeriod]); | |
| // Handle zodiac sign selection with auto-center | |
| const handleSignSelect = (signId) => { | |
| setSelectedSign(signId); | |
| }; | |
| return ( | |
| <div | |
| className={`min-h-screen transition-all duration-500 ${ | |
| isDarkMode ? "text-white" : "text-gray-800" | |
| }`} | |
| style={{ | |
| background: isDarkMode | |
| ? "linear-gradient(135deg, #7E437B 0%, #0B194A 50%, #221C96 100%)" | |
| : "linear-gradient(135deg, #A19BFF 0%, #FCC6EA 50%, #BADCFF 100%)", | |
| }} | |
| > | |
| <div className="max-w-6xl mx-auto p-5"> | |
| {/* Header Controls */} | |
| <div className="flex justify-between items-center mb-6 md:mb-8"> | |
| {/* Theme Toggle - Icon only for all screen sizes */} | |
| <button | |
| onClick={toggleTheme} | |
| className={`flex items-center justify-center w-12 h-12 rounded-full font-medium transition-all duration-300 shadow-lg hover:scale-105 ${ | |
| isDarkMode | |
| ? "bg-white bg-opacity-10 text-white hover:bg-opacity-20" | |
| : "bg-white bg-opacity-90 text-gray-700 hover:bg-opacity-100" | |
| }`} | |
| > | |
| <i | |
| className={`text-xl ${ | |
| isDarkMode ? "ri-sun-line" : "ri-moon-line" | |
| }`} | |
| ></i> | |
| </button> | |
| {/* App Icon - Visible on mobile - Fixed centering */} | |
| <div className="sm:hidden flex-1 flex justify-center"> | |
| <div className="inline-flex items-center justify-center w-12 h-12 bg-gradient-to-r from-blue-400 to-purple-500 rounded-full shadow-lg"> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width="24" | |
| height="24" | |
| viewBox="0 0 24 24" | |
| fill="none" | |
| stroke="currentColor" | |
| strokeWidth="2" | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| className="icon icon-tabler icons-tabler-outline icon-tabler-crystal-ball" | |
| > | |
| <path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |
| <path d="M6.73 17.018a8 8 0 1 1 10.54 0" /> | |
| <path d="M5 19a2 2 0 0 0 2 2h10a2 2 0 1 0 0 -4h-10a2 2 0 0 0 -2 2z" /> | |
| <path d="M11 7a3 3 0 0 0 -3 3" /> | |
| </svg> | |
| </div> | |
| </div> | |
| {/* Gender Selection - Toggle style on mobile, tabs on desktop */} | |
| <button | |
| onClick={() => | |
| setSelectedGender(selectedGender === "male" ? "female" : "male") | |
| } | |
| className={`flex items-center justify-center w-12 h-12 rounded-full font-medium transition-all duration-300 shadow-lg hover:scale-105 sm:hidden ${ | |
| isDarkMode | |
| ? "bg-white bg-opacity-10 text-white hover:bg-opacity-20" | |
| : "bg-white bg-opacity-90 text-gray-700 hover:bg-opacity-100" | |
| }`} | |
| > | |
| <i | |
| className={ | |
| selectedGender === "male" | |
| ? "ri-women-line text-xl" | |
| : "ri-men-line text-xl" | |
| } | |
| ></i> | |
| </button> | |
| {/* Gender Selection - Desktop tabs */} | |
| <div | |
| className={`hidden sm:flex rounded-full p-1 shadow-lg ${ | |
| isDarkMode ? "bg-white bg-opacity-10" : "bg-white bg-opacity-90" | |
| }`} | |
| > | |
| <button | |
| onClick={() => setSelectedGender("female")} | |
| className={`px-3 md:px-6 py-2 rounded-full font-medium transition-all ${ | |
| selectedGender === "female" | |
| ? "bg-pink-400 text-white shadow-md" | |
| : isDarkMode | |
| ? "text-white hover:text-pink-300" | |
| : "text-gray-600 hover:text-pink-400" | |
| }`} | |
| > | |
| <span>Female</span> | |
| </button> | |
| <button | |
| onClick={() => setSelectedGender("male")} | |
| className={`px-3 md:px-6 py-2 rounded-full font-medium transition-all ${ | |
| selectedGender === "male" | |
| ? "bg-blue-400 text-white shadow-md" | |
| : isDarkMode | |
| ? "text-white hover:text-blue-300" | |
| : "text-gray-600 hover:text-blue-400" | |
| }`} | |
| > | |
| <span>Male</span> | |
| </button> | |
| </div> | |
| </div> | |
| {/* App Title - Hidden on mobile */} | |
| <div className="text-center mb-6 md:mb-10 hidden sm:block"> | |
| <div className="inline-flex items-center justify-center w-16 h-16 bg-gradient-to-r from-blue-400 to-purple-500 rounded-full mb-4 shadow-lg"> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width="24" | |
| height="24" | |
| viewBox="0 0 24 24" | |
| fill="none" | |
| stroke="currentColor" | |
| strokeWidth="2" | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| className="icon icon-tabler icons-tabler-outline icon-tabler-crystal-ball" | |
| > | |
| <path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |
| <path d="M6.73 17.018a8 8 0 1 1 10.54 0" /> | |
| <path d="M5 19a2 2 0 0 0 2 2h10a2 2 0 1 0 0 -4h-10a2 2 0 0 0 -2 2z" /> | |
| <path d="M11 7a3 3 0 0 0 -3 3" /> | |
| </svg> | |
| </div> | |
| <h1 className="text-4xl font-bold mb-2">Horoscope App</h1> | |
| </div> | |
| {/* Zodiac Signs Grid - Single Row */} | |
| <div | |
| className="w-full overflow-x-auto mb-6 md:mb-10" | |
| ref={zodiacScrollRef} | |
| > | |
| <div className="flex justify-start md:justify-center gap-2 md:gap-3 pb-4 px-4 min-w-max py-2"> | |
| {zodiacSigns.map((sign) => ( | |
| <div | |
| key={sign.id} | |
| data-sign={sign.id} | |
| onClick={() => handleSignSelect(sign.id)} | |
| className="flex flex-col items-center cursor-pointer transition-all duration-300 hover:scale-105 flex-shrink-0 py-1" | |
| > | |
| <div | |
| className={`rounded-full p-0.5 transition-all duration-300 ${ | |
| selectedSign === sign.id | |
| ? "w-24 h-24 md:w-28 md:h-28 shadow-lg" | |
| : "w-20 h-20 md:w-24 md:h-24" | |
| }`} | |
| style={{ | |
| background: | |
| selectedSign === sign.id | |
| ? "linear-gradient(135deg, #1D3249 0%, #96D1E2 100%)" | |
| : "linear-gradient(135deg, #07090A 0%, #979999 100%)", | |
| }} | |
| > | |
| {/* Background layer for separation */} | |
| <div | |
| className={`w-full h-full rounded-full p-0.5 ${ | |
| isDarkMode ? "bg-gray-900" : "bg-gray-50" | |
| }`} | |
| > | |
| <div | |
| className={`w-full h-full rounded-full flex items-center justify-center text-xl overflow-hidden ${ | |
| isDarkMode ? "bg-gray-800" : "bg-gray-100" | |
| }`} | |
| > | |
| {getImagePath(sign) ? ( | |
| <img | |
| src={getImagePath(sign)} | |
| alt={sign.name} | |
| className="w-full h-full object-cover rounded-full" | |
| onError={(e) => { | |
| e.target.style.display = "none"; | |
| e.target.nextSibling.style.display = "block"; | |
| }} | |
| /> | |
| ) : ( | |
| <ZodiacIcon | |
| sign={sign.id} | |
| size={selectedSign === sign.id ? 30 : 28} | |
| className="text-gray-600" | |
| /> | |
| )} | |
| <span | |
| style={{ | |
| display: getImagePath(sign) ? "none" : "none", | |
| }} | |
| > | |
| {sign.emoji} | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| <p | |
| className={`text-xs font-medium mt-3 text-center flex items-center justify-center gap-1 transition-all duration-300 ${ | |
| selectedSign === sign.id ? "font-bold" : "" | |
| }`} | |
| > | |
| <ZodiacIcon | |
| sign={sign.id} | |
| size={selectedSign === sign.id ? 17 : 16} | |
| className="text-current" | |
| /> | |
| {sign.name} | |
| </p> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Period Selection */} | |
| <div className="flex justify-center mb-6 md:mb-8"> | |
| <div | |
| className={`flex rounded-full p-1 shadow-lg ${ | |
| isDarkMode ? "bg-white bg-opacity-10" : "bg-white bg-opacity-90" | |
| }`} | |
| > | |
| {periods.map((period) => ( | |
| <button | |
| key={period.id} | |
| onClick={() => setSelectedPeriod(period.id)} | |
| className={`px-4 md:px-6 py-2 md:py-3 rounded-full font-medium transition-all text-sm md:text-base flex items-center gap-1 ${ | |
| selectedPeriod === period.id | |
| ? "bg-blue-400 text-white shadow-md" | |
| : isDarkMode | |
| ? "text-white hover:text-blue-300" | |
| : "text-gray-600 hover:text-blue-400" | |
| }`} | |
| > | |
| {period.name} | |
| {period.id !== "today" && ( | |
| <i className="ri-lock-line text-xs opacity-70"></i> | |
| )} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Category Selection */} | |
| <div className="flex justify-center flex-wrap gap-2 md:gap-3 mb-6 md:mb-10"> | |
| {categories.map((category) => ( | |
| <button | |
| key={category.id} | |
| onClick={() => setSelectedCategory(category.id)} | |
| className={`flex items-center gap-2 px-3 md:px-4 py-2 rounded-full font-medium transition-all duration-300 shadow-lg hover:scale-105 text-sm md:text-base ${ | |
| selectedCategory === category.id | |
| ? "bg-purple-400 text-white shadow-lg" | |
| : isDarkMode | |
| ? "bg-white bg-opacity-10 text-white hover:bg-opacity-20" | |
| : "bg-white bg-opacity-90 text-gray-600 hover:bg-opacity-100" | |
| }`} | |
| > | |
| {category.icon} | |
| {category.name} | |
| </button> | |
| ))} | |
| </div> | |
| {/* Horoscope Content */} | |
| <div | |
| className={`rounded-3xl p-8 mb-4 shadow-2xl backdrop-blur-lg transition-all ${ | |
| isDarkMode | |
| ? "bg-white bg-opacity-10 border border-white border-opacity-20" | |
| : "bg-white bg-opacity-95" | |
| }`} | |
| > | |
| <div className="flex items-center gap-6 mb-8"> | |
| <div | |
| className="w-20 h-20 rounded-full p-0.5 shadow-lg" | |
| style={{ | |
| background: "linear-gradient(135deg, #1D3249 0%, #96D1E2 100%)", | |
| }} | |
| > | |
| {/* Background layer for separation */} | |
| <div | |
| className={`w-full h-full rounded-full p-0.5 ${ | |
| isDarkMode ? "bg-gray-900" : "bg-gray-50" | |
| }`} | |
| > | |
| <div | |
| className={`w-full h-full rounded-full flex items-center justify-center text-2xl overflow-hidden ${ | |
| isDarkMode ? "bg-gray-800" : "bg-gray-100" | |
| }`} | |
| > | |
| {getImagePath(getCurrentSign()) ? ( | |
| <img | |
| src={getImagePath(getCurrentSign())} | |
| alt={getCurrentSign()?.name} | |
| className="w-full h-full object-cover rounded-full" | |
| onError={(e) => { | |
| e.target.style.display = "none"; | |
| e.target.nextSibling.style.display = "block"; | |
| }} | |
| /> | |
| ) : ( | |
| <ZodiacIcon | |
| sign={getCurrentSign()?.id} | |
| size={32} | |
| className="text-gray-600" | |
| /> | |
| )} | |
| <span | |
| style={{ | |
| display: getImagePath(getCurrentSign()) ? "none" : "none", | |
| }} | |
| > | |
| {getCurrentSign()?.emoji} | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="flex-1"> | |
| <h2 className="text-3xl font-bold text-blue-400 flex items-center gap-2 mb-2"> | |
| {getPeriodTitle()} | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| width="24" | |
| height="24" | |
| viewBox="0 0 24 24" | |
| fill="none" | |
| stroke="currentColor" | |
| strokeWidth="2" | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| className="icon icon-tabler icons-tabler-outline icon-tabler-trending-up" | |
| > | |
| <path stroke="none" d="M0 0h24v24H0z" fill="none" /> | |
| <path d="M3 17l6 -6l4 4l8 -8" /> | |
| <path d="M14 7l7 0l0 7" /> | |
| </svg> | |
| </h2> | |
| <div className="flex items-center gap-2"> | |
| <ZodiacIcon | |
| sign={getCurrentSign()?.id} | |
| size={20} | |
| className="text-current" | |
| /> | |
| <span className="font-semibold"> | |
| {getCurrentSign()?.name} ({getCurrentSign()?.dates}) | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="text-lg leading-relaxed mb-8"> | |
| {isLoading ? ( | |
| <div className="flex items-center justify-center py-4"> | |
| <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-400"></div> | |
| <span className="ml-2">Loading your horoscope...</span> | |
| </div> | |
| ) : ( | |
| <p>{getHoroscopeText()}</p> | |
| )} | |
| {error && ( | |
| <div className="text-sm opacity-70 mt-2"> | |
| <i className="ri-information-line mr-1"></i> | |
| Using cached content | |
| </div> | |
| )} | |
| </div>{" "} | |
| </div> | |
| {/* Footer */} | |
| <div className="text-center text-sm opacity-70 mt-4 space-y-2"> | |
| <p className="flex items-center justify-center gap-2"> | |
| <i className="ri-sparkling-line"></i> | |
| Discover your daily horoscope and unlock the secrets of the stars | |
| <i className="ri-sparkling-line"></i> | |
| </p> | |
| <p className="text-xs opacity-60"> | |
| Β© 2025 AstroLens Horoscope - All rights reserved. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default HoroscopeApp; | |