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

extract login page components for DRY

Created reusable components:
- ValuePropCard: icon + title + description card
- StepCard: numbered step with color variant
- HeroSection: logo, title, firefly animation
- ValuePropsSection: 3 value prop cards
- HowItWorksSection: 4 step cards

byarielm.fyi 0f47c41f dd3141a6

verified
+47
src/components/login/HeroSection.tsx
··· 1 + import FireflyLogo from "../../assets/at-firefly-logo.svg?react"; 2 + 3 + interface HeroSectionProps { 4 + reducedMotion?: boolean; 5 + } 6 + 7 + export default function HeroSection({ reducedMotion = false }: HeroSectionProps) { 8 + return ( 9 + <div className="text-center md:text-left"> 10 + <div className="justify-center md:justify-start mb-4"> 11 + <div className="logo-glow-container"> 12 + <FireflyLogo className="w-50 h-15" /> 13 + </div> 14 + </div> 15 + 16 + <h1 className="text-5xl md:text-6xl font-bold bg-gradient-to-r from-purple-600 via-cyan-500 to-pink-500 dark:from-cyan-300 dark:via-purple-300 dark:to-pink-300 bg-clip-text text-transparent mb-3 md:mb-4"> 17 + ATlast 18 + </h1> 19 + <p className="text-xl md:text-2xl lg:text-2xl text-purple-900 dark:text-cyan-100 mb-2 font-medium"> 20 + Find Your Light in the ATmosphere 21 + </p> 22 + <p className="text-purple-750 dark:text-cyan-250 mb-6"> 23 + Reconnect with your internet, one firefly at a time ✨ 24 + </p> 25 + 26 + {/* Decorative firefly trail - only show if motion enabled */} 27 + {!reducedMotion && ( 28 + <div 29 + className="mt-8 flex justify-center md:justify-start space-x-2" 30 + aria-hidden="true" 31 + > 32 + {[...Array(5)].map((_, i) => ( 33 + <div 34 + key={i} 35 + className="w-2 h-2 rounded-full bg-orange-500 dark:bg-amber-400" 36 + style={{ 37 + opacity: 1 - i * 0.15, 38 + animation: `float ${2 + i * 0.3}s ease-in-out infinite`, 39 + animationDelay: `${i * 0.2}s`, 40 + }} 41 + /> 42 + ))} 43 + </div> 44 + )} 45 + </div> 46 + ); 47 + }
+37
src/components/login/HowItWorksSection.tsx
··· 1 + import StepCard from "./StepCard"; 2 + 3 + export default function HowItWorksSection() { 4 + return ( 5 + <div className="max-w-4xl mx-auto"> 6 + <h2 className="text-2xl font-bold text-center text-purple-950 dark:text-cyan-50 mb-8"> 7 + How It Works 8 + </h2> 9 + <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> 10 + <StepCard 11 + number={1} 12 + color="orange" 13 + title="Connect" 14 + description="Sign in with your ATmosphere account" 15 + /> 16 + <StepCard 17 + number={2} 18 + color="cyan" 19 + title="Upload" 20 + description="Import your following data from other platforms" 21 + /> 22 + <StepCard 23 + number={3} 24 + color="pink" 25 + title="Match" 26 + description="We find your fireflies in the ATmosphere" 27 + /> 28 + <StepCard 29 + number={4} 30 + color="amber" 31 + title="Follow" 32 + description="Reconnect with your community" 33 + /> 34 + </div> 35 + </div> 36 + ); 37 + }
+37
src/components/login/StepCard.tsx
··· 1 + interface StepCardProps { 2 + number: number; 3 + color: "orange" | "cyan" | "pink" | "amber"; 4 + title: string; 5 + description: string; 6 + } 7 + 8 + const colorClasses = { 9 + orange: "bg-orange-500", 10 + cyan: "bg-cyan-500", 11 + pink: "bg-pink-500", 12 + amber: "bg-amber-500", 13 + }; 14 + 15 + export default function StepCard({ 16 + number, 17 + color, 18 + title, 19 + description, 20 + }: StepCardProps) { 21 + return ( 22 + <div className="text-center"> 23 + <div 24 + className={`w-12 h-12 ${colorClasses[color]} text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md`} 25 + aria-hidden="true" 26 + > 27 + {number} 28 + </div> 29 + <h3 className="font-semibold text-purple-950 dark:text-cyan-50 mb-1"> 30 + {title} 31 + </h3> 32 + <p className="text-sm text-purple-900 dark:text-cyan-100"> 33 + {description} 34 + </p> 35 + </div> 36 + ); 37 + }
+27
src/components/login/ValuePropCard.tsx
··· 1 + import { LucideIcon } from "lucide-react"; 2 + 3 + interface ValuePropCardProps { 4 + icon: LucideIcon; 5 + title: string; 6 + description: string; 7 + } 8 + 9 + export default function ValuePropCard({ 10 + icon: Icon, 11 + title, 12 + description, 13 + }: ValuePropCardProps) { 14 + return ( 15 + <div className="bg-white/50 border-cyan-500/30 hover:border-cyan-400 dark:bg-slate-900/50 dark:border-purple-500/30 dark:hover:border-purple-400 backdrop-blur-xl rounded-2xl p-6 border-2 transition-all hover:scale-105 shadow-lg"> 16 + <div className="w-12 h-12 bg-gradient-to-br from-amber-300 to-orange-600 rounded-xl flex items-center justify-center mb-4 shadow-md"> 17 + <Icon className="w-6 h-6 text-slate-900" /> 18 + </div> 19 + <h3 className="text-lg font-bold text-purple-950 dark:text-cyan-50 mb-2"> 20 + {title} 21 + </h3> 22 + <p className="text-purple-750 dark:text-cyan-250 text-sm leading-relaxed"> 23 + {description} 24 + </p> 25 + </div> 26 + ); 27 + }
+24
src/components/login/ValuePropsSection.tsx
··· 1 + import { Heart, Upload, Search } from "lucide-react"; 2 + import ValuePropCard from "./ValuePropCard"; 3 + 4 + export default function ValuePropsSection() { 5 + return ( 6 + <div className="grid md:grid-cols-3 gap-4 md:gap-6 mb-12 md:mb-16 max-w-5xl mx-auto"> 7 + <ValuePropCard 8 + icon={Upload} 9 + title="Share Your Light" 10 + description="Import your following lists. Your data stays private, your connections shine bright." 11 + /> 12 + <ValuePropCard 13 + icon={Search} 14 + title="Find Your Swarm" 15 + description="Watch as fireflies light up - discover which friends have already migrated to the ATmosphere." 16 + /> 17 + <ValuePropCard 18 + icon={Heart} 19 + title="Sync Your Glow" 20 + description="Reconnect instantly. Follow everyone at once or pick and choose - light up together." 21 + /> 22 + </div> 23 + ); 24 + }