ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

ui color branding :')

+33 -3
src/App.tsx
··· 9 9 import { useSearch } from "./hooks/useSearch"; 10 10 import { useFollow } from "./hooks/useFollows"; 11 11 import { useFileUpload } from "./hooks/useFileUpload"; 12 + import { useTheme } from "./hooks/useTheme"; 13 + import ThemeControls from "./components/ThemeControls"; 14 + import Firefly from "./components/Firefly"; 15 + 12 16 13 17 export default function App() { 14 18 // Auth hook ··· 21 25 login, 22 26 logout, 23 27 } = useAuth(); 28 + 29 + // Theme hook 30 + const { isDark, reducedMotion, toggleTheme, toggleMotion } = useTheme(); 24 31 25 32 // Add state to track current platform 26 33 const [currentPlatform, setCurrentPlatform] = useState<string>('tiktok'); ··· 161 168 }; 162 169 163 170 return ( 164 - <div className="min-h-screen bg-gradient-to-br from-blue-50 to-purple-50 dark:from-gray-900 dark:to-gray-800"> 171 + <div className="min-h-screen relative overflow-hidden"> 172 + {/* Firefly particles - only render if motion not reduced */} 173 + {!reducedMotion && ( 174 + <div className="fixed inset-0 pointer-events-none" aria-hidden="true"> 175 + {[...Array(15)].map((_, i) => ( 176 + <Firefly key={i} delay={i * 0.5} duration={3 + Math.random() * 2} /> 177 + ))} 178 + </div> 179 + )} 180 + 181 + {/* Status message for screen readers */} 165 182 <div 166 183 role="status" 167 184 aria-live="polite" ··· 171 188 {statusMessage} 172 189 </div> 173 190 191 + {/* Skip to main content link */} 174 192 <a 175 193 href="#main-content" 176 - className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-blue-600 focus:text-white focus:px-4 focus:py-2 focus:rounded-lg" 194 + className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:bg-firefly-orange focus:text-white focus:px-4 focus:py-2 focus:rounded-lg" 177 195 > 178 196 Skip to main content 179 197 </a> ··· 183 201 {currentStep === 'checking' && ( 184 202 <div className="p-6 max-w-md mx-auto mt-8"> 185 203 <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-8 text-center space-y-4"> 186 - <div className="w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-600 rounded-2xl mx-auto flex items-center justify-center"> 204 + <div className="w-16 h-16 bbg-firefly-banner dark:bg-firefly-banner-dark text-white rounded-2xl mx-auto flex items-center justify-center"> 187 205 <ArrowRight className="w-8 h-8 text-white animate-pulse" /> 188 206 </div> 189 207 <h2 className="text-xl font-bold text-gray-900 dark:text-gray-100">Loading...</h2> ··· 198 216 onSubmit={handleLogin} 199 217 session={session} 200 218 onNavigate={setCurrentStep} 219 + reducedMotion={reducedMotion} 201 220 /> 202 221 )} 203 222 ··· 210 229 onFileUpload={processFileUpload} 211 230 onLoadUpload={handleLoadUpload} 212 231 currentStep={currentStep} 232 + reducedMotion={reducedMotion} 233 + isDark={isDark} 234 + onToggleTheme={toggleTheme} 235 + onToggleMotion={toggleMotion} 213 236 /> 214 237 )} 215 238 ··· 222 245 searchProgress={searchProgress} 223 246 currentStep={currentStep} 224 247 sourcePlatform={currentPlatform} 248 + isDark={isDark} 249 + onToggleTheme={toggleTheme} 250 + onToggleMotion={toggleMotion} 225 251 /> 226 252 )} 227 253 ··· 243 269 isFollowing={isFollowing} 244 270 currentStep={currentStep} 245 271 sourcePlatform={currentPlatform} 272 + reducedMotion={reducedMotion} 273 + isDark={isDark} 274 + onToggleTheme={toggleTheme} 275 + onToggleMotion={toggleMotion} 246 276 /> 247 277 )} 248 278 </main>
+80 -41
src/components/AppHeader.tsx
··· 1 1 import { useState, useEffect, useRef } from "react"; 2 2 import { Heart, Home, LogOut, ChevronDown } from "lucide-react"; 3 + import ThemeControls from "./ThemeControls"; 3 4 4 5 interface atprotoSession { 5 6 did: string; ··· 14 15 onLogout: () => void; 15 16 onNavigate: (step: 'home' | 'login') => void; 16 17 currentStep: string; 18 + isDark?: boolean; 19 + reducedMotion?: boolean; 20 + onToggleTheme?: () => void; 21 + onToggleMotion?: () => void; 17 22 } 18 23 19 - export default function AppHeader({ session, onLogout, onNavigate, currentStep }: AppHeaderProps) { 24 + export default function AppHeader({ 25 + session, 26 + onLogout, 27 + onNavigate, 28 + currentStep, 29 + isDark = false, 30 + reducedMotion = false, 31 + onToggleTheme, 32 + onToggleMotion 33 + }: AppHeaderProps) { 20 34 const [showMenu, setShowMenu] = useState(false); 21 35 const menuRef = useRef<HTMLDivElement>(null); 22 36 ··· 31 45 }, []); 32 46 33 47 return ( 34 - <div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"> 48 + <div className="bg-white/95 dark:bg-slate-800/95 border-b-2 border-slate-200 dark:border-slate-700 backdrop-blur-sm relative z-[100]"> 35 49 <div className="max-w-6xl mx-auto px-4 py-3"> 36 50 <div className="flex items-center justify-between"> 37 - <button onClick={() => onNavigate(session ? 'home' : 'login')} className="flex items-center space-x-3 hover:opacity-80 transition-opacity focus:outline-none focus:ring-2 focus:ring-blue-500 rounded-lg px-2 py-1"> 38 - <div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-purple-600 rounded-xl flex items-center justify-center"> 39 - <Heart className="w-5 h-5 text-white" /> 51 + <button 52 + onClick={() => onNavigate(session ? 'home' : 'login')} 53 + className="flex items-center space-x-3 hover:opacity-80 transition-opacity focus:outline-none focus:ring-2 focus:ring-firefly-orange rounded-lg px-2 py-1" 54 + > 55 + <div className="w-10 h-10 bg-gradient-to-br from-firefly-amber via-firefly-orange to-firefly-pink rounded-xl flex items-center justify-center shadow-md"> 56 + <Heart className="w-5 h-5 text-slate-900" /> 40 57 </div> 41 - <h1 className="text-xl font-bold text-gray-900 dark:text-gray-100">ATlast</h1> 58 + <h1 className="text-xl font-bold text-slate-900 dark:text-slate-100">ATlast</h1> 42 59 </button> 43 60 44 - {session && ( 45 - <div className="relative" ref={menuRef}> 46 - <button onClick={() => setShowMenu(!showMenu)} className="flex items-center space-x-3 px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500"> 47 - {session?.avatar ? ( 48 - <img src={session.avatar} alt="" className="w-8 h-8 rounded-full object-cover" /> 49 - ) : ( 50 - <div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full flex items-center justify-center"> 51 - <span className="text-white font-bold text-sm">{session?.handle?.charAt(0).toUpperCase()}</span> 61 + <div className="flex items-center space-x-4"> 62 + {onToggleTheme && onToggleMotion && ( 63 + <ThemeControls 64 + isDark={isDark} 65 + reducedMotion={reducedMotion} 66 + onToggleTheme={onToggleTheme} 67 + onToggleMotion={onToggleMotion} 68 + /> 69 + )} 70 + {session && ( 71 + <div className="relative z-[9999]" ref={menuRef}> 72 + <button 73 + onClick={() => setShowMenu(!showMenu)} 74 + className="flex items-center space-x-3 px-3 py-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors focus:outline-none focus:ring-2 focus:ring-firefly-orange" 75 + > 76 + {session?.avatar ? ( 77 + <img src={session.avatar} alt="" className="w-8 h-8 rounded-full object-cover" /> 78 + ) : ( 79 + <div className="w-8 h-8 bg-gradient-to-br from-firefly-cyan to-blue-500 rounded-full flex items-center justify-center shadow-sm"> 80 + <span className="text-white font-bold text-sm">{session?.handle?.charAt(0).toUpperCase()}</span> 81 + </div> 82 + )} 83 + <span className="text-sm font-medium text-slate-900 dark:text-slate-100 hidden sm:inline">@{session?.handle}</span> 84 + <ChevronDown className={`w-4 h-4 text-slate-600 dark:text-slate-400 transition-transform ${showMenu ? 'rotate-180' : ''}`} /> 85 + </button> 86 + 87 + {showMenu && ( 88 + <div className="absolute right-0 mt-2 w-64 bg-white dark:bg-slate-800 rounded-lg shadow-lg border-2 border-slate-200 dark:border-slate-700 py-2 z-[9999]"> 89 + <div className="px-4 py-3 border-b-2 border-slate-200 dark:border-slate-700"> 90 + <div className="font-semibold text-slate-900 dark:text-slate-100">{session?.displayName || session.handle}</div> 91 + <div className="text-sm text-slate-600 dark:text-slate-400">@{session?.handle}</div> 92 + </div> 93 + <button 94 + onClick={() => { setShowMenu(false); onNavigate('home'); }} 95 + className="w-full flex items-center space-x-3 px-4 py-2 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors text-left" 96 + > 97 + <Home className="w-4 h-4 text-slate-600 dark:text-slate-400" /> 98 + <span className="text-slate-900 dark:text-slate-100">Dashboard</span> 99 + </button> 100 + <button 101 + onClick={() => { setShowMenu(false); onNavigate('login'); }} 102 + className="w-full flex items-center space-x-3 px-4 py-2 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors text-left" 103 + > 104 + <Heart className="w-4 h-4 text-slate-600 dark:text-slate-400" /> 105 + <span className="text-slate-900 dark:text-slate-100">About</span> 106 + </button> 107 + <div className="border-t-2 border-slate-200 dark:border-slate-700 my-2"></div> 108 + <button 109 + onClick={() => { setShowMenu(false); onLogout(); }} 110 + className="w-full flex items-center space-x-3 px-4 py-2 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors text-left text-red-600 dark:text-red-400" 111 + > 112 + <LogOut className="w-4 h-4" /> 113 + <span>Log out</span> 114 + </button> 52 115 </div> 53 116 )} 54 - <span className="text-sm font-medium text-gray-900 dark:text-gray-100 hidden sm:inline">@{session?.handle}</span> 55 - <ChevronDown className={`w-4 h-4 text-gray-500 transition-transform ${showMenu ? 'rotate-180' : ''}`} /> 56 - </button> 57 - 58 - {showMenu && ( 59 - <div className="absolute right-0 mt-2 w-64 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-2 z-50"> 60 - <div className="px-4 py-3 border-b border-gray-200 dark:border-gray-700"> 61 - <div className="font-medium text-gray-900 dark:text-gray-100">{session?.displayName || session.handle}</div> 62 - <div className="text-sm text-gray-500 dark:text-gray-400">@{session?.handle}</div> 63 - </div> 64 - <button onClick={() => { setShowMenu(false); onNavigate('home'); }} className="w-full flex items-center space-x-3 px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-left"> 65 - <Home className="w-4 h-4 text-gray-500" /> 66 - <span className="text-gray-900 dark:text-gray-100">Dashboard</span> 67 - </button> 68 - <button onClick={() => { setShowMenu(false); onNavigate('login'); }} className="w-full flex items-center space-x-3 px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-left"> 69 - <Heart className="w-4 h-4 text-gray-500" /> 70 - <span className="text-gray-900 dark:text-gray-100">About</span> 71 - </button> 72 - <div className="border-t border-gray-200 dark:border-gray-700 my-2"></div> 73 - <button onClick={() => { setShowMenu(false); onLogout(); }} className="w-full flex items-center space-x-3 px-4 py-2 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors text-left text-red-600 dark:text-red-400"> 74 - <LogOut className="w-4 h-4" /> 75 - <span>Log out</span> 76 - </button> 77 - </div> 78 - )} 79 - </div> 80 - )} 117 + </div> 118 + )} 119 + </div> 81 120 </div> 82 121 </div> 83 122 </div>
+22
src/components/Firefly.tsx
··· 1 + interface FireflyProps { 2 + delay?: number; 3 + duration?: number; 4 + } 5 + 6 + export default function Firefly({ delay = 0, duration = 3 }: FireflyProps) { 7 + const style = { 8 + animation: `float ${duration}s ease-in-out ${delay}s infinite`, 9 + left: `${Math.random() * 100}%`, 10 + top: `${Math.random() * 100}%`, 11 + }; 12 + 13 + return ( 14 + <div 15 + className="absolute w-1 h-1 bg-firefly-amber dark:bg-firefly-glow rounded-full opacity-40 pointer-events-none" 16 + style={style} 17 + aria-hidden="true" 18 + > 19 + <div className="absolute inset-0 bg-firefly-glow dark:bg-firefly-amber rounded-full animate-pulse blur-sm" /> 20 + </div> 21 + ); 22 + }
+13 -13
src/components/SearchResultCard.tsx
··· 27 27 return ( 28 28 <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-sm overflow-hidden"> 29 29 {/* Source User */} 30 - <div className="px-4 py-3 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700"> 30 + <div className="px-4 py-3 bg-slate-50 dark:bg-slate-900/50 border-b-2 border-slate-200 dark:border-slate-700"> 31 31 <div className="flex items-start justify-between gap-2"> 32 32 <div className="flex-1 min-w-0"> 33 - <div className="flex flex-wrap items-baseline gap-x-2 gap-y-1"> 34 - <span className="font-bold text-gray-900 dark:text-gray-100 truncate"> 33 + <div className="flex flex-wrap items-center gap-x-2 gap-y-1"> 34 + <span className="font-bold text-slate-900 dark:text-slate-100 truncate text-base"> 35 35 @{result.sourceUser.username} 36 36 </span> 37 - <span className="text-xs text-gray-500 dark:text-gray-400 whitespace-nowrap"> 37 + <span className="text-sm text-slate-700 dark:text-slate-300 whitespace-nowrap"> 38 38 from {platform.name} 39 39 </span> 40 40 </div> 41 41 </div> 42 - <div className={`text-xs px-2 py-1 rounded-full ${platform.accentBg} text-white whitespace-nowrap flex-shrink-0`}> 42 + <div className={`text-xs px-2 py-1 rounded-full bg-indigo-700 dark:bg-pink-700/70 text-white whitespace-nowrap flex-shrink-0`}> 43 43 {result.atprotoMatches.length} {result.atprotoMatches.length === 1 ? 'match' : 'matches'} 44 44 </div> 45 45 </div> ··· 66 66 {match.avatar ? ( 67 67 <img 68 68 src={match.avatar} 69 - alt="User avatar, description not provided" 69 + alt="User avatar" 70 70 className="w-12 h-12 rounded-full object-cover flex-shrink-0" 71 71 /> 72 72 ) : ( ··· 84 84 {match.displayName} 85 85 </div> 86 86 )} 87 - <div className="text-sm text-gray-600 dark:text-gray-400"> 87 + <div className="text-sm text-gray-800 dark:text-gray-200"> 88 88 @{match.handle} 89 89 </div> 90 90 {match.description && ( 91 - <div className="text-sm text-gray-500 dark:text-gray-400 mt-1 line-clamp-2">{match.description}</div> 91 + <div className="text-sm text-gray-700 dark:text-gray-300 mt-1 line-clamp-2">{match.description}</div> 92 92 )} 93 93 {(match.postCount || match.followerCount) && ( 94 - <div className="flex items-center space-x-3 mt-2 text-xs text-gray-500 dark:text-gray-400"> 94 + <div className="flex items-center space-x-3 mt-2 text-xs text-gray-700 dark:text-gray-300"> 95 95 {match.postCount && match.postCount > 0 && ( 96 96 <span>{match.postCount.toLocaleString()} posts</span> 97 97 )} ··· 115 115 isFollowed 116 116 ? 'bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 cursor-not-allowed opacity-60' 117 117 : isSelected 118 - ? 'bg-blue-600 text-white' 119 - : 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600' 118 + ? 'bg-cyan-500 dark:bg-cyan-300 text-white dark:text-slate-700 shadow-md' 119 + : 'bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-600' 120 120 }`} 121 121 title={isFollowed ? 'Already followed' : isSelected ? 'Selected to follow' : 'Select to follow'} 122 122 > ··· 134 134 {hasMoreMatches && ( 135 135 <button 136 136 onClick={onToggleExpand} 137 - className="w-full py-2 text-sm text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium transition-colors flex items-center justify-center space-x-1" 137 + className="w-full py-2 text-sm text-cyan-700 hover:text-cyan-900 dark:text-cyan-400 dark:hover:text-cyan-200 font-medium transition-colors flex items-center justify-center space-x-1" 138 138 > 139 - <span>{isExpanded ? 'Show less' : `Show ${result.atprotoMatches.length - 1} more ${result.atprotoMatches.length - 1 === 1 ? 'match' : 'matches'}`}</span> 139 + <span>{isExpanded ? 'Show less' : `Show ${result.atprotoMatches.length - 1} more ${result.atprotoMatches.length - 1 === 1 ? 'option' : 'options'}`}</span> 140 140 <ChevronDown className={`w-4 h-4 transition-transform ${isExpanded ? 'rotate-180' : ''}`} /> 141 141 </button> 142 142 )}
+43
src/components/ThemeControls.tsx
··· 1 + import { Sun, Moon, Pause, Play } from 'lucide-react'; 2 + 3 + interface ThemeControlsProps { 4 + isDark: boolean; 5 + reducedMotion: boolean; 6 + onToggleTheme: () => void; 7 + onToggleMotion: () => void; 8 + } 9 + 10 + export default function ThemeControls({ 11 + isDark, 12 + reducedMotion, 13 + onToggleTheme, 14 + onToggleMotion 15 + }: ThemeControlsProps) { 16 + return ( 17 + <div className="flex items-center space-x-2"> 18 + <button 19 + onClick={onToggleMotion} 20 + className="p-2 bg-white/90 dark:bg-slate-800/90 backdrop-blur-sm rounded-lg border border-slate-200 dark:border-slate-700 hover:bg-white dark:hover:bg-slate-700 transition-colors shadow-lg" 21 + aria-label={reducedMotion ? "Enable animations" : "Reduce motion"} 22 + title={reducedMotion ? "Enable animations" : "Reduce motion"} 23 + > 24 + {reducedMotion ? ( 25 + <Play className="w-5 h-5 text-slate-700 dark:text-slate-300" /> 26 + ) : ( 27 + <Pause className="w-5 h-5 text-slate-700 dark:text-slate-300" /> 28 + )} 29 + </button> 30 + <button 31 + onClick={onToggleTheme} 32 + className="p-2 bg-white/90 dark:bg-slate-800/90 backdrop-blur-sm rounded-lg border border-slate-200 dark:border-slate-700 hover:bg-white dark:hover:bg-slate-700 transition-colors shadow-lg" 33 + aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"} 34 + > 35 + {isDark ? ( 36 + <Sun className="w-5 h-5 text-firefly-amber" /> 37 + ) : ( 38 + <Moon className="w-5 h-5 text-slate-700" /> 39 + )} 40 + </button> 41 + </div> 42 + ); 43 + }
+37
src/hooks/useTheme.ts
··· 1 + import { useState, useEffect } from 'react'; 2 + 3 + export function useTheme() { 4 + const [isDark, setIsDark] = useState(() => { 5 + // Check localStorage first, then system preference 6 + const stored = localStorage.getItem('theme'); 7 + if (stored) return stored === 'dark'; 8 + return window.matchMedia('(prefers-color-scheme: dark)').matches; 9 + }); 10 + 11 + const [reducedMotion, setReducedMotion] = useState(() => { 12 + return window.matchMedia('(prefers-reduced-motion: reduce)').matches; 13 + }); 14 + 15 + useEffect(() => { 16 + // Apply theme to document 17 + if (isDark) { 18 + document.documentElement.classList.add('dark'); 19 + } else { 20 + document.documentElement.classList.remove('dark'); 21 + } 22 + localStorage.setItem('theme', isDark ? 'dark' : 'light'); 23 + }, [isDark]); 24 + 25 + useEffect(() => { 26 + // Listen for system motion preference changes 27 + const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); 28 + const handler = (e: MediaQueryListEvent) => setReducedMotion(e.matches); 29 + mediaQuery.addEventListener('change', handler); 30 + return () => mediaQuery.removeEventListener('change', handler); 31 + }, []); 32 + 33 + const toggleTheme = () => setIsDark(!isDark); 34 + const toggleMotion = () => setReducedMotion(!reducedMotion); 35 + 36 + return { isDark, reducedMotion, toggleTheme, toggleMotion }; 37 + }
+33 -1
src/index.css
··· 5 5 @layer base { 6 6 body { 7 7 font-family: system-ui, sans-serif; 8 - @apply bg-gray-50 text-gray-900 dark:bg-gray-900 dark:text-gray-100; 8 + @apply bg-gradient-to-br from-amber-50 via-orange-50 to-pink-50 9 + dark:from-indigo-950 dark:via-purple-900 dark:to-slate-900 10 + text-slate-900 dark:text-slate-100 11 + transition-colors duration-300; 9 12 } 10 13 11 14 button { 12 15 cursor: pointer; 16 + } 17 + } 18 + 19 + /* Firefly animation keyframes */ 20 + @keyframes float { 21 + 0%, 100% { 22 + transform: translate(0, 0) scale(1); 23 + opacity: 0.3; 24 + } 25 + 25% { 26 + transform: translate(10px, -20px) scale(1.2); 27 + opacity: 0.8; 28 + } 29 + 50% { 30 + transform: translate(-5px, -40px) scale(1); 31 + opacity: 0.5; 32 + } 33 + 75% { 34 + transform: translate(15px, -25px) scale(1.1); 35 + opacity: 0.9; 36 + } 37 + } 38 + 39 + @keyframes glow-pulse { 40 + 0%, 100% { 41 + box-shadow: 0 0 20px rgba(251, 191, 36, 0.3); 42 + } 43 + 50% { 44 + box-shadow: 0 0 40px rgba(251, 191, 36, 0.6), 0 0 60px rgba(251, 191, 36, 0.3); 13 45 } 14 46 }
+61 -33
src/pages/Home.tsx
··· 1 - import { Upload, History, FileText } from "lucide-react"; 1 + import { Upload, History, FileText, Sparkles } from "lucide-react"; 2 2 import { useState, useEffect, useRef } from "react"; 3 3 import AppHeader from "../components/AppHeader"; 4 4 import PlatformSelector from "../components/PlatformSelector"; ··· 20 20 onFileUpload: (e: React.ChangeEvent<HTMLInputElement>, platform: string) => void; 21 21 onLoadUpload: (uploadId: string) => void; 22 22 currentStep: string; 23 + reducedMotion?: boolean; 24 + isDark?: boolean; 25 + onToggleTheme?: () => void; 26 + onToggleMotion?: () => void; 23 27 } 24 28 25 29 export default function HomePage({ ··· 28 32 onNavigate, 29 33 onFileUpload, 30 34 onLoadUpload, 31 - currentStep 35 + currentStep, 36 + reducedMotion = false, 37 + isDark = false, 38 + onToggleTheme, 39 + onToggleMotion 32 40 }: HomePageProps) { 33 41 const [uploads, setUploads] = useState<UploadType[]>([]); 34 42 const [isLoading, setIsLoading] = useState(true); ··· 80 88 }; 81 89 82 90 return ( 83 - <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800"> 84 - <AppHeader session={session} onLogout={onLogout} onNavigate={onNavigate} currentStep={currentStep} /> 91 + <div className="min-h-screen"> 92 + <AppHeader 93 + session={session} 94 + onLogout={onLogout} 95 + onNavigate={onNavigate} 96 + currentStep={currentStep} 97 + isDark={isDark} 98 + reducedMotion={reducedMotion} 99 + onToggleTheme={onToggleTheme} 100 + onToggleMotion={onToggleMotion} 101 + /> 85 102 86 103 <div className="max-w-4xl mx-auto px-4 py-8 space-y-6"> 87 104 {/* Upload Section */} 88 - <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6"> 105 + <div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-2 border-slate-200 dark:border-slate-700"> 89 106 <div className="flex items-center space-x-3 mb-4"> 90 - <Upload className="w-6 h-6 text-blue-600 dark:text-blue-400" /> 91 - <h2 className="text-xl font-bold text-gray-900 dark:text-gray-100"> 92 - Upload Following Data 93 - </h2> 107 + <div 108 + className={`w-12 h-12 bg-gradient-to-br from-firefly-amber to-firefly-orange rounded-xl flex items-center justify-center shadow-md ${ 109 + reducedMotion ? '' : 'animate-glow-pulse' 110 + }`} 111 + > 112 + <Upload className="w-6 h-6 text-slate-900" /> 113 + </div> 114 + <div> 115 + <h2 className="text-xl font-bold text-slate-900 dark:text-slate-100"> 116 + Light Up Your Network 117 + </h2> 118 + <p className="text-sm text-slate-700 dark:text-slate-300"> 119 + Upload your data to find your fireflies 120 + </p> 121 + </div> 94 122 </div> 95 - <p className="text-gray-600 dark:text-gray-400 mb-6"> 96 - Click a platform below to upload your exported data and find matches on the ATmosphere 123 + <p className="text-slate-700 dark:text-slate-300 mb-6"> 124 + Click a platform below to upload your exported data and discover matches in the ATmosphere 97 125 </p> 98 126 99 127 <PlatformSelector onPlatformSelect={handlePlatformSelect} /> ··· 109 137 aria-label="Upload following data file" 110 138 /> 111 139 112 - <div className="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-xl"> 113 - <p className="text-sm text-blue-900 dark:text-blue-300"> 114 - 💡 <strong>How to get your data:</strong> 140 + <div className="mt-4 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-xl border-2 border-blue-200 dark:border-blue-800/30"> 141 + <p className="text-sm text-blue-900 dark:text-blue-300 font-semibold"> 142 + 💡 How to get your data: 115 143 </p> 116 144 <p className="text-sm text-blue-900 dark:text-blue-300 mt-2"> 117 145 <strong>TikTok:</strong> Profile → Settings → Account → Download your data → Upload Following.txt ··· 123 151 </div> 124 152 125 153 {/* Upload History Section */} 126 - <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6"> 154 + <div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-2 border-slate-200 dark:border-slate-700"> 127 155 <div className="flex items-center space-x-3 mb-6"> 128 - <History className="w-6 h-6 text-purple-600 dark:text-purple-400" /> 129 - <h2 className="text-xl font-bold text-gray-900 dark:text-gray-100"> 130 - Previous Uploads 156 + <Sparkles className="w-6 h-6 text-firefly-amber" /> 157 + <h2 className="text-xl font-bold text-slate-900 dark:text-slate-100"> 158 + Your Light Trail 131 159 </h2> 132 160 </div> 133 161 134 162 {isLoading ? ( 135 163 <div className="space-y-3"> 136 164 {[...Array(3)].map((_, i) => ( 137 - <div key={i} className="animate-pulse flex items-center space-x-4 p-4 bg-gray-50 dark:bg-gray-700 rounded-xl"> 138 - <div className="w-12 h-12 bg-gray-200 dark:bg-gray-600 rounded-xl" /> 165 + <div key={i} className="animate-pulse flex items-center space-x-4 p-4 bg-slate-50 dark:bg-slate-700 rounded-xl"> 166 + <div className="w-12 h-12 bg-slate-200 dark:bg-slate-600 rounded-xl" /> 139 167 <div className="flex-1 space-y-2"> 140 - <div className="h-4 bg-gray-200 dark:bg-gray-600 rounded w-3/4" /> 141 - <div className="h-3 bg-gray-200 dark:bg-gray-600 rounded w-1/2" /> 168 + <div className="h-4 bg-slate-200 dark:bg-slate-600 rounded w-3/4" /> 169 + <div className="h-3 bg-slate-200 dark:bg-slate-600 rounded w-1/2" /> 142 170 </div> 143 171 </div> 144 172 ))} 145 173 </div> 146 174 ) : uploads.length === 0 ? ( 147 175 <div className="text-center py-12"> 148 - <FileText className="w-16 h-16 text-gray-300 dark:text-gray-600 mx-auto mb-4" /> 149 - <p className="text-gray-500 dark:text-gray-400">No previous uploads yet</p> 150 - <p className="text-sm text-gray-400 dark:text-gray-500 mt-2"> 176 + <FileText className="w-16 h-16 text-slate-300 dark:text-slate-600 mx-auto mb-4" /> 177 + <p className="text-slate-600 dark:text-slate-400 font-medium">No previous uploads yet</p> 178 + <p className="text-sm text-slate-500 dark:text-slate-500 mt-2"> 151 179 Upload your first file to get started 152 180 </p> 153 181 </div> ··· 157 185 <button 158 186 key={upload.uploadId} 159 187 onClick={() => onLoadUpload(upload.uploadId)} 160 - className="w-full flex items-start space-x-4 p-4 bg-gray-50 dark:bg-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 rounded-xl transition-colors text-left" 188 + className="w-full flex items-start space-x-4 p-4 bg-slate-50 dark:bg-slate-900/50 hover:bg-slate-100 dark:hover:bg-slate-900/70 rounded-xl transition-all text-left border-2 border-slate-200 dark:border-slate-700 hover:border-firefly-orange dark:hover:border-firefly-orange shadow-md hover:shadow-lg" 161 189 > 162 - <div className={`w-12 h-12 bg-gradient-to-r ${getPlatformColor(upload.sourcePlatform)} rounded-xl flex items-center justify-center flex-shrink-0`}> 163 - <Upload className="w-6 h-6 text-white" /> 190 + <div className={`w-12 h-12 bg-gradient-to-r ${getPlatformColor(upload.sourcePlatform)} rounded-xl flex items-center justify-center flex-shrink-0 shadow-md`}> 191 + <Sparkles className="w-6 h-6 text-white" /> 164 192 </div> 165 193 <div className="flex-1 min-w-0"> 166 194 <div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-2 mb-1"> 167 - <div className="font-semibold text-gray-900 dark:text-gray-100 capitalize"> 195 + <div className="font-semibold text-slate-900 dark:text-slate-100 capitalize"> 168 196 {upload.sourcePlatform} 169 197 </div> 170 198 <div className="flex items-center gap-2 flex-shrink-0"> 171 - <span className="text-xs px-2 py-0.5 bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300 rounded-full whitespace-nowrap"> 172 - {upload.matchedUsers} {upload.matchedUsers === 1 ? 'match' : 'matches'} 199 + <span className="text-xs px-2 py-0.5 bg-firefly-amber/20 dark:bg-firefly-amber/30 text-amber-900 dark:text-firefly-glow rounded-full font-medium border border-firefly-amber/20 dark:border-firefly-amber/50 whitespace-nowrap"> 200 + {upload.matchedUsers} {upload.matchedUsers === 1 ? 'firefly' : 'fireflies'} 173 201 </span> 174 - <div className="text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap"> 202 + <div className="text-sm text-slate-600 dark:text-slate-400 font-medium whitespace-nowrap"> 175 203 {Math.round((upload.matchedUsers / upload.totalUsers) * 100)}% 176 204 </div> 177 205 </div> 178 206 </div> 179 - <div className="text-sm text-gray-600 dark:text-gray-400"> 207 + <div className="text-sm text-slate-700 dark:text-slate-300"> 180 208 {upload.totalUsers} users • {formatDate(upload.createdAt)} 181 209 </div> 182 210 </div>
+52 -28
src/pages/Loading.tsx
··· 1 - import { Search } from "lucide-react"; 1 + import { Search, Sparkles } from "lucide-react"; 2 2 import AppHeader from "../components/AppHeader"; 3 3 import { PLATFORMS } from "../constants/platforms"; 4 4 ··· 23 23 searchProgress: SearchProgress; 24 24 currentStep: string; 25 25 sourcePlatform: string; 26 + isDark?: boolean; 27 + reducedMotion?: boolean; 28 + onToggleTheme?: () => void; 29 + onToggleMotion?: () => void; 26 30 } 27 31 28 - export default function LoadingPage({ session, onLogout, onNavigate, searchProgress, currentStep, sourcePlatform }: LoadingPageProps) { 32 + export default function LoadingPage({ 33 + session, 34 + onLogout, 35 + onNavigate, 36 + searchProgress, 37 + currentStep, 38 + sourcePlatform, 39 + isDark = false, 40 + reducedMotion = false, 41 + onToggleTheme, 42 + onToggleMotion 43 + }: LoadingPageProps) { 29 44 const platform = PLATFORMS[sourcePlatform] || PLATFORMS.tiktok; 30 45 const PlatformIcon = platform.icon; 31 46 32 47 return ( 33 - <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800"> 34 - <AppHeader session={session} onLogout={onLogout} onNavigate={onNavigate} currentStep={currentStep} /> 48 + <div className="min-h-screen"> 49 + <AppHeader 50 + session={session} 51 + onLogout={onLogout} 52 + onNavigate={onNavigate} 53 + currentStep={currentStep} 54 + isDark={isDark} 55 + reducedMotion={reducedMotion} 56 + onToggleTheme={onToggleTheme} 57 + onToggleMotion={onToggleMotion} 58 + /> 35 59 36 60 {/* Platform Banner - Searching State */} 37 - <div className={`bg-gradient-to-r ${platform.color} text-white`}> 61 + <div className={`bg-firefly-banner dark:bg-firefly-banner-dark text-white`}> 38 62 <div className="max-w-3xl mx-auto px-4 py-6"> 39 63 <div className="flex items-center justify-between"> 40 64 <div className="flex items-center space-x-4"> ··· 43 67 <Search className="w-6 h-6 absolute -bottom-1 -right-1 animate-pulse" aria-hidden="true" /> 44 68 </div> 45 69 <div> 46 - <h2 className="text-xl font-bold">Finding Your People</h2> 70 + <h2 className="text-xl font-bold">Finding Your Fireflies</h2> 47 71 <p className="text-white/90 text-sm"> 48 72 Searching the ATmosphere for {platform.name} follows... 49 73 </p> ··· 60 84 </div> 61 85 62 86 {/* Progress Stats */} 63 - <div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"> 87 + <div className="bg-white/95 dark:bg-slate-800/95 border-b-2 border-slate-200 dark:border-slate-700 backdrop-blur-sm"> 64 88 <div className="max-w-3xl mx-auto px-4 py-4"> 65 89 <div className="grid grid-cols-3 gap-4 text-center mb-4"> 66 90 <div> 67 - <div className="text-2xl font-bold text-gray-900 dark:text-gray-100" aria-label={`${searchProgress.searched} searched`}> 91 + <div className="text-2xl font-bold text-slate-900 dark:text-slate-100" aria-label={`${searchProgress.searched} searched`}> 68 92 {searchProgress.searched} 69 93 </div> 70 - <div className="text-sm text-gray-600 dark:text-gray-300">Searched</div> 94 + <div className="text-sm text-slate-700 dark:text-slate-300 font-medium">Searched</div> 71 95 </div> 72 96 <div> 73 - <div className="text-2xl font-bold text-blue-600 dark:text-blue-400" aria-label={`${searchProgress.found} found`}> 97 + <div className="text-2xl font-bold text-firefly-orange" aria-label={`${searchProgress.found} found`}> 74 98 {searchProgress.found} 75 99 </div> 76 - <div className="text-sm text-gray-600 dark:text-gray-300">Found</div> 100 + <div className="text-sm text-slate-700 dark:text-slate-300 font-medium">Fireflies Found</div> 77 101 </div> 78 102 <div> 79 - <div className="text-2xl font-bold text-gray-400 dark:text-gray-500" aria-label={`${searchProgress.total} total`}> 103 + <div className="text-2xl font-bold text-slate-600 dark:text-slate-400" aria-label={`${searchProgress.total} total`}> 80 104 {searchProgress.total} 81 105 </div> 82 - <div className="text-sm text-gray-600 dark:text-gray-300">Total</div> 106 + <div className="text-sm text-slate-700 dark:text-slate-300 font-medium">Total</div> 83 107 </div> 84 108 </div> 85 109 86 110 <div 87 - className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-3" 111 + className="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-3" 88 112 role="progressbar" 89 113 aria-valuenow={searchProgress.total > 0 ? Math.round((searchProgress.searched / searchProgress.total) * 100) : 0} 90 114 aria-valuemin={0} 91 115 aria-valuemax={100} 92 116 > 93 117 <div 94 - className="bg-gradient-to-r from-blue-500 to-purple-600 h-full rounded-full transition-all" 118 + className="bg-gradient-to-r from-firefly-amber via-firefly-orange to-firefly-pink h-full rounded-full transition-all" 95 119 style={{ width: `${searchProgress.total > 0 ? (searchProgress.searched / searchProgress.total) * 100 : 0}%` }} 96 120 /> 97 121 </div> ··· 101 125 {/* Skeleton Results - Matches layout of Results page */} 102 126 <div className="max-w-3xl mx-auto px-4 py-4 space-y-4"> 103 127 {[...Array(8)].map((_, i) => ( 104 - <div key={i} className="bg-white dark:bg-gray-800 rounded-2xl shadow-sm overflow-hidden animate-pulse"> 128 + <div key={i} className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-2xl shadow-sm overflow-hidden animate-pulse border-2 border-slate-200 dark:border-slate-700"> 105 129 {/* Source User Skeleton */} 106 - <div className="p-4 bg-gray-50 dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700"> 130 + <div className="p-4 bg-slate-50 dark:bg-slate-900/50 border-b-2 border-slate-200 dark:border-slate-700"> 107 131 <div className="flex items-center space-x-3"> 108 - <div className="w-10 h-10 bg-gray-300 dark:bg-gray-600 rounded-full" /> 132 + <div className="w-10 h-10 bg-slate-300 dark:bg-slate-600 rounded-full" /> 109 133 <div className="flex-1 space-y-2"> 110 - <div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-32" /> 111 - <div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-24" /> 134 + <div className="h-4 bg-slate-300 dark:bg-slate-600 rounded w-32" /> 135 + <div className="h-3 bg-slate-200 dark:bg-slate-700 rounded w-24" /> 112 136 </div> 113 - <div className="h-5 w-16 bg-gray-300 dark:bg-gray-600 rounded-full" /> 137 + <div className="h-5 w-16 bg-slate-300 dark:bg-slate-600 rounded-full" /> 114 138 </div> 115 139 </div> 116 140 117 141 {/* Match Skeleton */} 118 142 <div className="p-4"> 119 - <div className="flex items-start space-x-3 p-3 rounded-xl bg-blue-50 dark:bg-blue-900/20"> 120 - <div className="w-12 h-12 bg-gray-300 dark:bg-gray-600 rounded-full flex-shrink-0" /> 143 + <div className="flex items-start space-x-3 p-3 rounded-xl bg-amber-50 dark:bg-amber-900/10 border-2 border-amber-200 dark:border-amber-800/30"> 144 + <div className="w-12 h-12 bg-slate-300 dark:bg-slate-600 rounded-full flex-shrink-0" /> 121 145 <div className="flex-1 space-y-2"> 122 - <div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-3/4" /> 123 - <div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-1/2" /> 124 - <div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-full" /> 125 - <div className="h-5 w-20 bg-green-200 dark:bg-green-900 rounded-full mt-2" /> 146 + <div className="h-4 bg-slate-300 dark:bg-slate-600 rounded w-3/4" /> 147 + <div className="h-3 bg-slate-200 dark:bg-slate-700 rounded w-1/2" /> 148 + <div className="h-3 bg-slate-200 dark:bg-slate-700 rounded w-full" /> 149 + <div className="h-5 w-20 bg-slate-300 dark:bg-slate-600 rounded-full mt-2" /> 126 150 </div> 127 - <div className="w-20 h-8 bg-gray-300 dark:bg-gray-600 rounded-full flex-shrink-0" /> 151 + <div className="w-20 h-8 bg-slate-300 dark:bg-slate-600 rounded-full flex-shrink-0" /> 128 152 </div> 129 153 </div> 130 154 </div>
+101 -64
src/pages/Login.tsx
··· 5 5 onSubmit: (handle: string) => void; 6 6 session?: { handle: string } | null; 7 7 onNavigate?: (step: 'home') => void; 8 + reducedMotion?: boolean; 8 9 } 9 10 10 - export default function LoginPage({ onSubmit, session, onNavigate }: LoginPageProps) { 11 + export default function LoginPage({ onSubmit, session, onNavigate, reducedMotion = false }: LoginPageProps) { 11 12 const [handle, setHandle] = useState(""); 12 13 13 14 const handleSubmit = (e: React.FormEvent) => { ··· 16 17 }; 17 18 18 19 return ( 19 - <div className="min-h-screen bg-gradient-to-br from-blue-50 via-purple-50 to-pink-50 dark:from-gray-900 dark:via-gray-850 dark:to-gray-800"> 20 + <div className="min-h-screen"> 20 21 <div className="max-w-6xl mx-auto px-4 py-8 md:py-12"> 21 22 22 23 {/* Hero Section - Side by side on desktop */} 23 24 <div className="grid md:grid-cols-2 gap-8 md:gap-12 items-start mb-12 md:mb-16"> 24 25 {/* Left: Welcome */} 25 26 <div className="text-center md:text-left"> 26 - <div className="inline-flex items-center justify-center w-20 h-20 md:w-24 md:h-24 bg-gradient-to-br from-blue-500 to-purple-600 rounded-3xl mb-4 md:mb-6 shadow-xl"> 27 - <Heart className="w-10 h-10 md:w-12 md:h-12 text-white" /> 27 + <div className="inline-flex items-center justify-center mb-6 relative"> 28 + <div 29 + className={`w-20 h-20 md:w-24 md:h-24 bg-gradient-to-br from-firefly-amber via-firefly-orange to-firefly-pink rounded-3xl flex items-center justify-center relative shadow-xl ${ 30 + reducedMotion ? '' : 'animate-glow-pulse' 31 + }`} 32 + > 33 + <Heart className="w-10 h-10 md:w-12 md:h-12 text-slate-900" aria-hidden="true" /> 34 + {/* Firefly mascot hint */} 35 + <div 36 + className={`absolute -top-2 -right-2 w-8 h-8 bg-firefly-glow rounded-full flex items-center justify-center shadow-lg ${ 37 + reducedMotion ? '' : 'animate-bounce' 38 + }`} 39 + aria-hidden="true" 40 + > 41 + <div className="w-4 h-4 bg-firefly-amber rounded-full" /> 42 + </div> 43 + </div> 28 44 </div> 29 - <h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-gray-100 mb-3 md:mb-4"> 30 - Welcome to ATlast 45 + 46 + <h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-slate-900 dark:text-white mb-3 md:mb-4"> 47 + ATlast 31 48 </h1> 32 - <p className="text-lg md:text-xl lg:text-2xl text-gray-700 dark:text-gray-300 mb-6"> 33 - Reunite with your community on the ATmosphere 49 + <p className="text-lg md:text-xl lg:text-2xl text-slate-800 dark:text-slate-100 mb-2 font-medium"> 50 + Find Your Light in the ATmosphere 51 + </p> 52 + <p className="text-slate-700 dark:text-slate-300 mb-6"> 53 + Reconnect with your internet, one firefly at a time ✨ 34 54 </p> 35 55 56 + {/* Decorative firefly trail - only show if motion enabled */} 57 + {!reducedMotion && ( 58 + <div className="mt-8 flex justify-center md:justify-start space-x-2" aria-hidden="true"> 59 + {[...Array(5)].map((_, i) => ( 60 + <div 61 + key={i} 62 + className="w-2 h-2 rounded-full bg-firefly-amber dark:bg-firefly-glow" 63 + style={{ 64 + opacity: 1 - i * 0.15, 65 + animation: `float ${2 + i * 0.3}s ease-in-out infinite`, 66 + animationDelay: `${i * 0.2}s` 67 + }} 68 + /> 69 + ))} 70 + </div> 71 + )} 72 + 36 73 {/* Privacy Notice - visible on mobile */} 37 74 <div className="md:hidden mt-6"> 38 - <p className="text-sm text-gray-600 dark:text-gray-400"> 39 - Your data is processed and stored by our servers if you enable DM notifications. This is to help you find matches and reconnect with your community. 75 + <p className="text-sm text-slate-600 dark:text-slate-400"> 76 + Your data is processed and stored by our servers. This helps you find matches and reconnect with your community. 40 77 </p> 41 78 </div> 42 79 </div> ··· 44 81 {/* Right: Login Card or Dashboard Button */} 45 82 <div className="w-full"> 46 83 {session ? ( 47 - <div className="bg-white dark:bg-gray-800 rounded-3xl shadow-2xl p-8 border border-gray-100 dark:border-gray-700"> 84 + <div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-3xl shadow-2xl p-8 border-2 border-slate-200 dark:border-slate-700"> 48 85 <div className="text-center mb-6"> 49 - <div className="w-16 h-16 bg-gradient-to-br from-blue-500 to-purple-600 rounded-full mx-auto mb-4 flex items-center justify-center"> 50 - <Heart className="w-8 h-8 text-white" /> 86 + <div className="w-16 h-16 bg-gradient-to-br from-firefly-amber via-firefly-orange to-firefly-pink rounded-full mx-auto mb-4 flex items-center justify-center shadow-md"> 87 + <Heart className="w-8 h-8 text-slate-900" /> 51 88 </div> 52 - <h2 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-2"> 89 + <h2 className="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-2"> 53 90 You're logged in! 54 91 </h2> 55 - <p className="text-gray-600 dark:text-gray-400"> 92 + <p className="text-slate-700 dark:text-slate-300"> 56 93 Welcome back, @{session.handle} 57 94 </p> 58 95 </div> 59 96 60 97 <button 61 98 onClick={() => onNavigate?.('home')} 62 - className="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white py-4 rounded-xl font-bold text-lg transition-all shadow-lg hover:shadow-xl focus:ring-4 focus:ring-purple-300 dark:focus:ring-purple-800 focus:outline-none flex items-center justify-center space-x-2" 99 + className="w-full bg-gradient-to-r from-firefly-amber via-firefly-orange to-firefly-pink hover:from-amber-600 hover:via-orange-600 hover:to-pink-600 text-white py-4 rounded-xl font-bold text-lg transition-all shadow-lg hover:shadow-xl focus:ring-4 focus:ring-orange-300 dark:focus:ring-orange-800 focus:outline-none flex items-center justify-center space-x-2" 63 100 > 64 101 <span>Go to Dashboard</span> 65 102 <ArrowRight className="w-5 h-5" /> 66 103 </button> 67 104 </div> 68 105 ) : ( 69 - <div className="bg-white dark:bg-gray-800 rounded-3xl shadow-2xl p-6 md:p-8 border border-gray-100 dark:border-gray-700"> 70 - <h2 className="text-xl md:text-2xl font-bold text-gray-900 dark:text-gray-100 mb-2 text-center"> 71 - Get Started 106 + <div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-3xl shadow-2xl p-6 md:p-8 border-2 border-slate-200 dark:border-slate-700"> 107 + <h2 className="text-xl md:text-2xl font-bold text-slate-900 dark:text-slate-100 mb-2 text-center"> 108 + Light Up Your Network 72 109 </h2> 73 - <p className="text-gray-600 dark:text-gray-400 text-center mb-6"> 110 + <p className="text-slate-700 dark:text-slate-300 text-center mb-6"> 74 111 Connect your ATmosphere account to begin 75 112 </p> 76 113 77 114 <form onSubmit={handleSubmit} className="space-y-4" method="post"> 78 115 <div> 79 - <label htmlFor="atproto-handle" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> 116 + <label htmlFor="atproto-handle" className="block text-sm font-semibold text-slate-900 dark:text-slate-100 mb-2"> 80 117 Your ATmosphere Handle 81 118 </label> 82 119 <input ··· 85 122 value={handle} 86 123 onChange={(e) => setHandle(e.target.value)} 87 124 placeholder="yourname.bsky.social" 88 - className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-xl bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all" 125 + className="w-full px-4 py-3 bg-slate-50 dark:bg-slate-900/50 border-2 border-slate-300 dark:border-slate-600 rounded-xl text-slate-900 dark:text-slate-100 placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-firefly-orange focus:border-transparent transition-all" 89 126 aria-required="true" 90 127 aria-describedby="handle-description" 91 128 /> 92 - <p id="handle-description" className="text-xs text-gray-500 dark:text-gray-400 mt-2"> 129 + <p id="handle-description" className="text-xs text-slate-600 dark:text-slate-400 mt-2"> 93 130 Enter your full ATmosphere handle (e.g., username.bsky.social or yourname.com) 94 131 </p> 95 132 </div> 96 133 97 134 <button 98 135 type="submit" 99 - className="w-full bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white py-4 rounded-xl font-bold text-lg transition-all shadow-lg hover:shadow-xl focus:ring-4 focus:ring-purple-300 dark:focus:ring-purple-800 focus:outline-none" 136 + className="w-full bg-gradient-to-r from-firefly-amber via-firefly-orange to-firefly-pink hover:from-amber-600 hover:via-orange-600 hover:to-pink-600 text-white py-4 rounded-xl font-bold text-lg transition-all shadow-lg hover:shadow-xl focus:ring-4 focus:ring-orange-300 dark:focus:ring-orange-800 focus:outline-none" 100 137 aria-label="Connect to the ATmosphere" 101 138 > 102 - Connect to the ATmosphere 139 + Join the Swarm ✨ 103 140 </button> 104 141 </form> 105 142 106 - <div className="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700"> 107 - <div className="flex items-start space-x-2 text-sm text-gray-600 dark:text-gray-400"> 143 + <div className="mt-6 pt-6 border-t-2 border-slate-200 dark:border-slate-700"> 144 + <div className="flex items-start space-x-2 text-sm text-slate-700 dark:text-slate-300"> 108 145 <svg className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"> 109 146 <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" /> 110 147 </svg> 111 148 <div> 112 - <p className="font-medium text-gray-700 dark:text-gray-300">Secure OAuth Connection</p> 149 + <p className="font-semibold text-slate-900 dark:text-slate-100">Secure OAuth Connection</p> 113 150 <p className="text-xs mt-1">We use official AT Protocol OAuth. We never see your password and you can revoke access anytime.</p> 114 151 </div> 115 152 </div> ··· 121 158 122 159 {/* Value Props */} 123 160 <div className="grid md:grid-cols-3 gap-4 md:gap-6 mb-12 md:mb-16 max-w-5xl mx-auto"> 124 - <div className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-lg border border-gray-100 dark:border-gray-700"> 125 - <div className="w-12 h-12 bg-blue-100 dark:bg-blue-900/30 rounded-xl flex items-center justify-center mb-4"> 126 - <Upload className="w-6 h-6 text-blue-600 dark:text-blue-400" /> 161 + <div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-2xl p-6 shadow-lg border-2 border-slate-200 dark:border-slate-700 hover:border-firefly-orange dark:hover:border-firefly-orange transition-all"> 162 + <div className="w-12 h-12 bg-gradient-to-br from-firefly-amber to-firefly-orange rounded-xl flex items-center justify-center mb-4 shadow-md"> 163 + <Upload className="w-6 h-6 text-slate-900" /> 127 164 </div> 128 - <h3 className="text-lg font-bold text-gray-900 dark:text-gray-100 mb-2"> 129 - Upload Your Data 165 + <h3 className="text-lg font-bold text-slate-900 dark:text-slate-100 mb-2"> 166 + Share Your Light 130 167 </h3> 131 - <p className="text-gray-600 dark:text-gray-400 text-sm"> 132 - Import your following lists from Twitter, TikTok, Instagram, and more. Your data stays private. 168 + <p className="text-slate-700 dark:text-slate-300 text-sm leading-relaxed"> 169 + Import your following lists. Your data stays private, your connections shine bright. 133 170 </p> 134 171 </div> 135 172 136 - <div className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-lg border border-gray-100 dark:border-gray-700"> 137 - <div className="w-12 h-12 bg-purple-100 dark:bg-purple-900/30 rounded-xl flex items-center justify-center mb-4"> 138 - <Search className="w-6 h-6 text-purple-600 dark:text-purple-400" /> 173 + <div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-2xl p-6 shadow-lg border-2 border-slate-200 dark:border-slate-700 hover:border-firefly-orange dark:hover:border-firefly-orange transition-all"> 174 + <div className="w-12 h-12 bg-gradient-to-br from-firefly-cyan to-blue-500 rounded-xl flex items-center justify-center mb-4 shadow-md"> 175 + <Search className="w-6 h-6 text-slate-900" /> 139 176 </div> 140 - <h3 className="text-lg font-bold text-gray-900 dark:text-gray-100 mb-2"> 141 - Find Matches 177 + <h3 className="text-lg font-bold text-slate-900 dark:text-slate-100 mb-2"> 178 + Find Your Swarm 142 179 </h3> 143 - <p className="text-gray-600 dark:text-gray-400 text-sm"> 144 - We'll search the ATmosphere to find which of your follows have already migrated. 180 + <p className="text-slate-700 dark:text-slate-300 text-sm leading-relaxed"> 181 + Watch as fireflies light up - discover which friends have already migrated to the ATmosphere. 145 182 </p> 146 183 </div> 147 184 148 - <div className="bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-lg border border-gray-100 dark:border-gray-700"> 149 - <div className="w-12 h-12 bg-pink-100 dark:bg-pink-900/30 rounded-xl flex items-center justify-center mb-4"> 150 - <Heart className="w-6 h-6 text-pink-600 dark:text-pink-400" /> 185 + <div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-2xl p-6 shadow-lg border-2 border-slate-200 dark:border-slate-700 hover:border-firefly-orange dark:hover:border-firefly-orange transition-all"> 186 + <div className="w-12 h-12 bg-gradient-to-br from-firefly-pink to-purple-500 rounded-xl flex items-center justify-center mb-4 shadow-md"> 187 + <Heart className="w-6 h-6 text-slate-900" /> 151 188 </div> 152 - <h3 className="text-lg font-bold text-gray-900 dark:text-gray-100 mb-2"> 153 - Reconnect Instantly 189 + <h3 className="text-lg font-bold text-slate-900 dark:text-slate-100 mb-2"> 190 + Sync Your Glow 154 191 </h3> 155 - <p className="text-gray-600 dark:text-gray-400 text-sm"> 156 - Follow everyone at once or pick and choose. Build your community on the ATmosphere. 192 + <p className="text-slate-700 dark:text-slate-300 text-sm leading-relaxed"> 193 + Reconnect instantly. Follow everyone at once or pick and choose - light up together. 157 194 </p> 158 195 </div> 159 196 </div> 160 197 161 198 {/* Privacy Notice - desktop only */} 162 199 <div className="hidden md:block text-center mb-8"> 163 - <p className="text-sm text-gray-600 dark:text-gray-400 max-w-2xl mx-auto"> 164 - Your data is processed and stored by our servers if you enable DM notifications. This is to help you find matches and reconnect with your community. 200 + <p className="text-sm text-slate-600 dark:text-slate-400 max-w-2xl mx-auto"> 201 + Your data is processed and stored by our servers. This helps you find matches and reconnect with your community. 165 202 </p> 166 203 </div> 167 204 168 205 {/* How It Works */} 169 206 <div className="max-w-4xl mx-auto"> 170 - <h2 className="text-2xl font-bold text-center text-gray-900 dark:text-gray-100 mb-8"> 207 + <h2 className="text-2xl font-bold text-center text-slate-900 dark:text-slate-100 mb-8"> 171 208 How It Works 172 209 </h2> 173 210 <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> 174 211 <div className="text-center"> 175 - <div className="w-12 h-12 bg-blue-500 text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg" aria-hidden="true"> 212 + <div className="w-12 h-12 bg-firefly-orange text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" aria-hidden="true"> 176 213 1 177 214 </div> 178 - <h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">Connect</h3> 179 - <p className="text-sm text-gray-600 dark:text-gray-400">Sign in with your ATmosphere account</p> 215 + <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">Connect</h3> 216 + <p className="text-sm text-slate-700 dark:text-slate-300">Sign in with your ATmosphere account</p> 180 217 </div> 181 218 <div className="text-center"> 182 - <div className="w-12 h-12 bg-purple-500 text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg" aria-hidden="true"> 219 + <div className="w-12 h-12 bg-firefly-pink text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" aria-hidden="true"> 183 220 2 184 221 </div> 185 - <h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">Upload</h3> 186 - <p className="text-sm text-gray-600 dark:text-gray-400">Import your following data from other platforms</p> 222 + <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">Upload</h3> 223 + <p className="text-sm text-slate-700 dark:text-slate-300">Import your following data from other platforms</p> 187 224 </div> 188 225 <div className="text-center"> 189 - <div className="w-12 h-12 bg-pink-500 text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg" aria-hidden="true"> 226 + <div className="w-12 h-12 bg-firefly-cyan text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" aria-hidden="true"> 190 227 3 191 228 </div> 192 - <h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">Match</h3> 193 - <p className="text-sm text-gray-600 dark:text-gray-400">We find your people on the ATmosphere</p> 229 + <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">Match</h3> 230 + <p className="text-sm text-slate-700 dark:text-slate-300">We find your fireflies in the ATmosphere</p> 194 231 </div> 195 232 <div className="text-center"> 196 - <div className="w-12 h-12 bg-orange-500 text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg" aria-hidden="true"> 233 + <div className="w-12 h-12 bg-firefly-amber text-slate-900 rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" aria-hidden="true"> 197 234 4 198 235 </div> 199 - <h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">Follow</h3> 200 - <p className="text-sm text-gray-600 dark:text-gray-400">Reconnect with your community</p> 236 + <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">Follow</h3> 237 + <p className="text-sm text-slate-700 dark:text-slate-300">Reconnect with your community</p> 201 238 </div> 202 239 </div> 203 240 </div>
+53 -18
src/pages/Results.tsx
··· 1 - import { Video, Heart } from "lucide-react"; 1 + import { Sparkles, Heart } from "lucide-react"; 2 2 import { PLATFORMS } from "../constants/platforms"; 3 3 import AppHeader from "../components/AppHeader"; 4 4 import SearchResultCard from "../components/SearchResultCard"; ··· 41 41 isFollowing: boolean; 42 42 currentStep: string; 43 43 sourcePlatform: string; 44 + reducedMotion?: boolean; 45 + isDark?: boolean; 46 + onToggleTheme?: () => void; 47 + onToggleMotion?: () => void; 44 48 } 45 49 46 50 export default function ResultsPage({ ··· 58 62 totalFound, 59 63 isFollowing, 60 64 currentStep, 61 - sourcePlatform 65 + sourcePlatform, 66 + reducedMotion = false, 67 + isDark = false, 68 + onToggleTheme, 69 + onToggleMotion 62 70 }: ResultsPageProps) { 63 71 const platform = PLATFORMS[sourcePlatform] || PLATFORMS.tiktok; 64 72 const PlatformIcon = platform.icon; 65 73 66 74 return ( 67 - <div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 pb-24"> 68 - <AppHeader session={session} onLogout={onLogout} onNavigate={onNavigate} currentStep={currentStep} /> 75 + <div className="min-h-screen pb-24"> 76 + <AppHeader 77 + session={session} 78 + onLogout={onLogout} 79 + onNavigate={onNavigate} 80 + currentStep={currentStep} 81 + isDark={isDark} 82 + reducedMotion={reducedMotion} 83 + onToggleTheme={onToggleTheme} 84 + onToggleMotion={onToggleMotion} 85 + /> 69 86 70 87 {/* Platform Info Banner */} 71 - <div className={`bg-gradient-to-r ${platform.color} text-white`}> 72 - <div className="max-w-3xl mx-auto px-4 py-6"> 88 + <div className="bg-firefly-banner dark:bg-firefly-banner-dark text-white relative overflow-hidden"> 89 + {!reducedMotion && ( 90 + <div className="absolute inset-0 opacity-20" aria-hidden="true"> 91 + {[...Array(10)].map((_, i) => ( 92 + <div 93 + key={i} 94 + className="absolute w-1 h-1 bg-white rounded-full" 95 + style={{ 96 + left: `${Math.random() * 100}%`, 97 + top: `${Math.random() * 100}%`, 98 + animation: `float ${2 + Math.random()}s ease-in-out infinite`, 99 + animationDelay: `${Math.random()}s` 100 + }} 101 + /> 102 + ))} 103 + </div> 104 + )} 105 + <div className="max-w-3xl mx-auto px-4 py-6 relative"> 73 106 <div className="flex items-center justify-between"> 74 107 <div className="flex items-center space-x-4"> 75 - <PlatformIcon className="w-12 h-12" /> 108 + <div className="w-12 h-12 bg-white/20 backdrop-blur rounded-xl flex items-center justify-center shadow-lg"> 109 + <Sparkles className="w-6 h-6 text-white" /> 110 + </div> 76 111 <div> 77 - <h2 className="text-xl font-bold">{platform.name} Matches</h2> 78 - <p className="text-white/90 text-sm"> 79 - {totalFound} matches from {searchResults.length} follows 112 + <h2 className="text-xl font-bold">{totalFound} Connections Found!</h2> 113 + <p className="text-white/95 text-sm"> 114 + From {searchResults.length} {platform.name} follows 80 115 </p> 81 116 </div> 82 117 </div> 83 118 {totalSelected > 0 && ( 84 119 <div className="text-right"> 85 120 <div className="text-2xl font-bold">{totalSelected}</div> 86 - <div className="text-xs text-white/80">selected</div> 121 + <div className="text-xs font-medium">selected</div> 87 122 </div> 88 123 )} 89 124 </div> ··· 91 126 </div> 92 127 93 128 {/* Action Buttons */} 94 - <div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 sticky top-0 z-10"> 129 + <div className="bg-white/95 dark:bg-slate-800/95 border-b-2 border-slate-200 dark:border-slate-700 sticky top-0 z-10 backdrop-blur-sm"> 95 130 <div className="max-w-3xl mx-auto px-4 py-3 flex space-x-2"> 96 131 <button 97 132 onClick={onSelectAll} 98 - className="flex-1 bg-blue-500 hover:bg-blue-600 text-white py-3 rounded-lg text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2" 133 + className="flex-1 bg-orange-600 hover:bg-orange-700 text-white py-3 rounded-xl text-sm font-semibold transition-all shadow-md hover:shadow-lg focus:outline-none focus:ring-4 focus:ring-orange-300 dark:focus:ring-orange-800" 99 134 type="button" 100 135 > 101 136 Select All 102 137 </button> 103 138 <button 104 139 onClick={onDeselectAll} 105 - className="flex-1 bg-gray-500 hover:bg-gray-600 text-white py-3 rounded-lg text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2" 140 + className="flex-1 bg-slate-600 dark:bg-slate-700 hover:bg-slate-700 dark:hover:bg-slate-600 text-white py-3 rounded-xl text-sm font-semibold transition-all shadow-md hover:shadow-lg focus:outline-none focus:ring-4 focus:ring-slate-400" 106 141 type="button" 107 142 > 108 143 Clear ··· 148 183 149 184 {/* Fixed Bottom Action Bar */} 150 185 {totalSelected > 0 && ( 151 - <div className="fixed bottom-0 left-0 right-0 bg-gradient-to-t from-white via-white to-transparent dark:from-gray-900 dark:via-gray-900 dark:to-transparent pt-8 pb-6"> 186 + <div className="fixed bottom-0 left-0 right-0 bg-gradient-to-t from-white via-white to-transparent dark:from-slate-900 dark:via-slate-900 dark:to-transparent pt-8 pb-6"> 152 187 <div className="max-w-3xl mx-auto px-4"> 153 188 <button 154 189 onClick={onFollowSelected} 155 190 disabled={isFollowing} 156 - className="w-full bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 hover:from-blue-600 hover:via-purple-600 hover:to-pink-600 text-white py-5 rounded-2xl font-bold text-lg transition-all shadow-2xl hover:shadow-3xl flex items-center justify-center space-x-3 transform hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none focus:outline-none focus:ring-4 focus:ring-purple-300 dark:focus:ring-purple-800" 191 + className="w-full bg-firefly-banner dark:bg-firefly-banner-dark text-white hover:from-amber-600 hover:via-orange-600 hover:to-pink-600 text-white py-5 rounded-2xl font-bold text-lg transition-all shadow-2xl hover:shadow-3xl flex items-center justify-center space-x-3 hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 focus:outline-none focus:ring-4 focus:ring-orange-300 dark:focus:ring-orange-800" 157 192 > 158 - <Heart className="w-6 h-6" /> 159 - <span>Follow {totalSelected} Selected {totalSelected === 1 ? 'User' : 'Users'}</span> 193 + <Sparkles className="w-6 h-6" /> 194 + <span>Light Up {totalSelected} Connection{totalSelected === 1 ? '' : 's'} ✨</span> 160 195 </button> 161 196 </div> 162 197 </div>
+34 -2
tailwind.config.js
··· 1 1 /** @type {import('tailwindcss').Config} */ 2 2 export default { 3 - darkMode: 'media', // use system prefs 3 + darkMode: 'class', // Changed from 'media' to 'class' for manual control 4 4 content: [ 5 5 "./index.html", 6 6 "./src/**/*.{js,ts,jsx,tsx}", 7 7 ], 8 8 theme: { 9 - extend: {}, 9 + extend: { 10 + colors: { 11 + firefly: { 12 + glow: '#FCD34D', // close to amber-300 13 + amber: '#F59E0B', // close to amber-500 14 + orange: '#F97316', // close to orange-500 15 + pink: '#EC4899', // close to tailwind pink-500 16 + cyan: '#10D2F4', // close to tailwind cyan-300 17 + } 18 + }, 19 + backgroundImage: { 20 + 'firefly-banner': 21 + 'linear-gradient(90deg, rgba(9,163,190,1) 0%, rgba(91,33,182,1) 33%, rgba(236,72,153,1) 67%, rgba(244,105,6,1) 100%)', 22 + 'firefly-banner-dark': 23 + 'linear-gradient(90deg, rgba(24,21,60,1) 0%, rgba(55,20,94,1) 33%, rgba(104,25,98,1) 67%, rgba(36,16,54,1) 100%)', 24 + }, 25 + animation: { 26 + 'float': 'float 3s ease-in-out infinite', 27 + 'glow-pulse': 'glow-pulse 3s ease-in-out infinite', 28 + }, 29 + keyframes: { 30 + float: { 31 + '0%, 100%': { transform: 'translate(0, 0) scale(1)', opacity: '0.3' }, 32 + '25%': { transform: 'translate(10px, -20px) scale(1.2)', opacity: '0.8' }, 33 + '50%': { transform: 'translate(-5px, -40px) scale(1)', opacity: '0.5' }, 34 + '75%': { transform: 'translate(15px, -25px) scale(1.1)', opacity: '0.9' }, 35 + }, 36 + 'glow-pulse': { 37 + '0%, 100%': { boxShadow: '0 0 20px rgba(251, 191, 36, 0.3)' }, 38 + '50%': { boxShadow: '0 0 40px rgba(251, 191, 36, 0.6), 0 0 60px rgba(251, 191, 36, 0.3)' }, 39 + }, 40 + }, 41 + }, 10 42 }, 11 43 plugins: [], 12 44 }