diff --git a/apps/client/app/(auth)/login.tsx b/apps/client/app/(auth)/login.tsx index d74f14d..91d56d0 100644 --- a/apps/client/app/(auth)/login.tsx +++ b/apps/client/app/(auth)/login.tsx @@ -35,10 +35,10 @@ export default function LoginScreen() { > - - ๐ŸŽ“ - - Welcome to Universify + + CMUnify + + Welcome back Sign in with your CMU account Use your @andrew.cmu.edu or @cmu.edu email @@ -65,7 +65,7 @@ export default function LoginScreen() { - New to Universify? + New to CMUnify? maxWidth: 440, backgroundColor: colors.surface, borderRadius: 16, + borderWidth: 1, + borderColor: colors.border, padding: 32, shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 8, - elevation: 4, + shadowOffset: { width: 0, height: 8 }, + shadowOpacity: 0.06, + shadowRadius: 24, + elevation: 3, }, cardMobile: { padding: 24, - borderRadius: 12, + borderRadius: 14, }, logoContainer: { alignItems: 'center', marginBottom: 32, }, - logo: { - width: 80, - height: 80, - borderRadius: 40, - backgroundColor: colors.primary, - justifyContent: 'center', - alignItems: 'center', - marginBottom: 16, + wordmark: { + fontSize: 22 * fontScale, + fontWeight: '800', + letterSpacing: -0.5, + color: colors.textPrimary, + marginBottom: 20, }, - logoText: { - fontSize: 40, + wordmarkAccent: { + color: colors.primary, }, title: { - fontSize: 28 * fontScale, - fontWeight: 'bold', + fontSize: 26 * fontScale, + fontWeight: '800', + letterSpacing: -0.5, color: colors.textPrimary, marginBottom: 8, }, diff --git a/apps/client/app/event/[id].tsx b/apps/client/app/event/[id].tsx index 5585e68..34e9b3a 100644 --- a/apps/client/app/event/[id].tsx +++ b/apps/client/app/event/[id].tsx @@ -285,19 +285,20 @@ const createStyles = (colors: AppPalette, fontScale: number) => color: colors.textPrimary, }, badge: { - backgroundColor: '#8B7FFF', + backgroundColor: 'rgba(139, 127, 255, 0.14)', paddingHorizontal: 8, paddingVertical: 4, - borderRadius: 4, + borderRadius: 6, }, socialBadge: { - backgroundColor: '#FF6BA8', + backgroundColor: 'rgba(255, 107, 168, 0.14)', }, badgeText: { fontSize: 10 * fontScale, - fontWeight: '600', - color: colors.onPrimary, + fontWeight: '700', + color: '#8B7FFF', textTransform: 'uppercase', + letterSpacing: 0.4, }, infoRow: { flexDirection: 'row', diff --git a/apps/client/app/index.tsx b/apps/client/app/index.tsx index e9d2353..dc7a0f9 100644 --- a/apps/client/app/index.tsx +++ b/apps/client/app/index.tsx @@ -6,9 +6,7 @@ import { ScrollView, TouchableOpacity, Animated, - Dimensions, - Platform, - Easing, + ActivityIndicator, } from 'react-native'; import { router } from 'expo-router'; import { useAuth } from '@/contexts/AuthContext'; @@ -17,822 +15,701 @@ import { Ionicons } from '@expo/vector-icons'; import { useAppTheme } from '@/hooks/useAppTheme'; import { AppPalette } from '@/constants/theme'; -// Get screen dimensions to use in calculations -const { width, height } = Dimensions.get('window'); - /** - * LandingPage Component - * - * This is the main entry point for unauthenticated users. - * It displays a marketing landing page with: - * 1. Hero Section (Logo, Headline, CTAs) - * 2. Features Grid (List of app capabilities) - * 3. Stats Section (Social proof) - * 4. Final Call to Action + * Landing page for unauthenticated users. + * + * Design principles (see repo docs / PR description for sources): + * - Type-led hero with a 5-second-clarity value proposition + * - Product-first: a preview of real event cards instead of abstract art + * - Accent color reserved for actions; calm neutral surfaces elsewhere + * - Hairline borders over heavy shadows; generous whitespace + * - Fully theme-aware (light/dark/high-contrast) and reduced-motion friendly */ export default function LandingPage() { const { isAuthenticated, isLoading } = useAuth(); const { isMobile, isDesktop } = useResponsive(); - const { colors, fontScale } = useAppTheme(); + const { colors, fontScale, reduceMotion } = useAppTheme(); const styles = React.useMemo(() => createStyles(colors, fontScale), [colors, fontScale]); - // Effect: Redirect to main app if already logged in + const fadeAnim = useRef(new Animated.Value(0)).current; + useEffect(() => { if (!isLoading && isAuthenticated) { router.replace('/(tabs)'); } }, [isAuthenticated, isLoading]); - // Show loading spinner while checking auth status + useEffect(() => { + if (reduceMotion) { + fadeAnim.setValue(1); + return; + } + Animated.timing(fadeAnim, { toValue: 1, duration: 500, useNativeDriver: true }).start(); + }, [fadeAnim, reduceMotion]); + if (isLoading) { return ( - ๐ŸŽ“ + ); } - // Don't render anything if authenticated (will redirect) if (isAuthenticated) { return null; } return ( - {/* Gradient Background & Animated Blobs */} - - - - - {/* Hero Section: The top part with "Welcome to CMUnify" */} - - - - - {/* Features Section: Grid of cards explaining what the app does */} - - - - - {/* Stats Section: Numbers showing usage/trust */} - - - - - {/* CTA Section: Bottom "Get Started" button */} - - - - - ); -} + + {/* Top bar */} + + + CMUnify + + router.push('/(auth)/login')} + > + Sign in + + -/** - * SpinningPetalLogo Component - * - * Replaces the old StarLogo. - * This creates a flower/star shape using 3 overlapping ellipses rotated at different angles. - */ -const SpinningPetalLogo = () => { - const { colors, fontScale } = useAppTheme(); - const styles = React.useMemo(() => createStyles(colors, fontScale), [colors, fontScale]); - return ( - - {/* First Ellipse - Vertical */} - - {/* Second Ellipse - Rotated 60 degrees */} - - {/* Third Ellipse - Rotated -60 degrees */} - - - ); -}; + {/* Hero */} + + + Built by students, for CMU + + + + Every campus event.{'\n'}One place. + + + + CMUnify pulls events out of scattered Slack channels, Discord servers, + and mailing lists into a single calendar โ€” with recommendations that + learn what you actually go to. + + + + router.push('/(auth)/signup')} + activeOpacity={0.85} + > + Get started + + + router.push('/(auth)/login')} + activeOpacity={0.7} + > + Sign in with CMU account + + + + + Free forever ยท @andrew.cmu.edu sign-in ยท Google Calendar sync + + + {/* Product preview: a stack of event cards */} + + + + + -/** - * AnimatedBlobs Component - * - * These are the large moving background shapes (the "side thing"). - * NOW UPDATED: To mimic the spinning petal logo style but large and in the background. - * It uses 3 large ellipses that rotate slowly. - */ -const AnimatedBlobs = () => { - const { colors, fontScale } = useAppTheme(); - const styles = React.useMemo(() => createStyles(colors, fontScale), [colors, fontScale]); - // Animation for rotation - const spinAnim = useRef(new Animated.Value(0)).current; + {/* Features */} + + WHY CMUNIFY + Stop missing things you'd love + + + + + + + + + - useEffect(() => { - // Infinite rotation loop - Animated.loop( - Animated.timing(spinAnim, { - toValue: 1, - duration: 30000, // 30 seconds for full rotation - easing: Easing.linear, - useNativeDriver: true, - }) - ).start(); - }, [spinAnim]); + {/* Stats */} + + + + + + + - // Interpolate 0-1 to 0-360deg - const spin = spinAnim.interpolate({ - inputRange: [0, 1], - outputRange: ['0deg', '360deg'], - }); + {/* Final CTA */} + + Your campus, in one feed + + Sign in with your CMU Google account and see this week's events in seconds. + + router.push('/(auth)/signup')} + activeOpacity={0.85} + > + Get started free + + + - return ( - - {/* The spinning container holding the petals */} - - {/* Petal 1 */} - - {/* Petal 2 */} - - {/* Petal 3 */} - + {/* Footer */} + + + Made with care at Carnegie Mellon ยท ScottyLabs Labrador ยท ยฉ 2026 CMUnify + + - + ); -}; - -/** - * AnimatedHero Component - * - * Displays the main welcome message and logo with entrance animations. - */ -const AnimatedHero: React.FC<{ isMobile: boolean }> = ({ isMobile }) => { - const { colors, fontScale } = useAppTheme(); - const styles = React.useMemo(() => createStyles(colors, fontScale), [colors, fontScale]); - const fadeAnim = useRef(new Animated.Value(0)).current; - const slideAnim = useRef(new Animated.Value(30)).current; - - // Run entrance animation on mount - useEffect(() => { - Animated.parallel([ - Animated.timing(fadeAnim, { - toValue: 1, - duration: 1200, - delay: 300, - useNativeDriver: true, - }), - Animated.spring(slideAnim, { - toValue: 0, - delay: 300, - tension: 40, - friction: 8, - useNativeDriver: true, - }), - ]).start(); - }, [fadeAnim, slideAnim]); +} +/* โ”€โ”€โ”€ Sections โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ + +function PreviewCard({ + styles, + colors, + accent, + title, + meta, + chips, + going, + offset, + elevated, +}: { + styles: ReturnType; + colors: AppPalette; + accent: string; + title: string; + meta: string; + chips: string[]; + going: number; + offset: 'left' | 'center' | 'right'; + elevated?: boolean; +}) { return ( - - {/* Logo Section */} - - - + + + + {title} + + + {meta} + + + + {chips.map((chip) => ( + + {chip} + + ))} + + + + {going} going + - - {/* Main Headline */} - - Welcome to - - - CMUnify - - - {/* Subheadline */} - - Your campus life, unified in one place.{'\n'} - Discover events, connect with clubs, and never miss what matters. - - - {/* CTA Buttons */} - - router.push('/(auth)/signup')} - activeOpacity={0.9} - > - Get Started - - - router.push('/(auth)/login')} - activeOpacity={0.9} - > - Sign In - - - - {/* Trust Indicators */} - - - โœ“ - Free Forever - - - โœ“ - 1000+ Students - - - โœ“ - 50+ Clubs - - - - ); -}; - -/** - * FeaturesGrid Component - * - * Renders the list of features (Calendar, Discovery, etc.) - */ -const FeaturesGrid: React.FC<{ isDesktop: boolean }> = ({ isDesktop }) => { - const { colors, fontScale } = useAppTheme(); - const styles = React.useMemo(() => createStyles(colors, fontScale), [colors, fontScale]); - const features = [ - { - iconName: 'calendar-outline', - title: 'Unified Calendar', - description: 'All campus events in one beautiful calendar view', - gradient: ['#FF6B6B', '#FF8E8E'], - }, - { - iconName: 'search-outline', - title: 'Smart Discovery', - description: 'AI-powered search to find exactly what you need', - gradient: ['#8B7FFF', '#A89FFF'], - }, - { - iconName: 'people-outline', - title: 'Social Events', - description: 'Create and join casual meetups instantly', - gradient: ['#FF6BA8', '#FF8EBF'], - }, - { - iconName: 'business-outline', - title: 'Club Hub', - description: 'Stay connected with all your organizations', - gradient: ['#4ECDC4', '#6FD9D1'], - }, - { - iconName: 'notifications-outline', - title: 'Smart Alerts', - description: 'Get notified about events you care about', - gradient: ['#FFD93D', '#FFE066'], - }, - { - iconName: 'phone-portrait-outline', - title: 'Cross-Platform', - description: 'Access from web, iOS, or Android seamlessly', - gradient: ['#95E1D3', '#ADE8DC'], - }, - ]; - - return ( - <> - Everything you need - - Powerful features designed for student life - - - - {features.map((feature, index) => ( - - ))} - - + ); -}; +} -/** - * FeatureCard Component - * - * Individual card for a feature with staggered entrance animation. - */ -const FeatureCard: React.FC<{ - iconName: string; +function Feature({ + styles, + colors, + icon, + title, + body, + isDesktop, +}: { + styles: ReturnType; + colors: AppPalette; + icon: keyof typeof Ionicons.glyphMap; title: string; - description: string; - gradient: string[]; - index: number; -}> = ({ iconName, title, description, gradient, index }) => { - const { colors, fontScale } = useAppTheme(); - const styles = React.useMemo(() => createStyles(colors, fontScale), [colors, fontScale]); - const fadeAnim = useRef(new Animated.Value(0)).current; - const scaleAnim = useRef(new Animated.Value(0.9)).current; - - useEffect(() => { - Animated.parallel([ - Animated.timing(fadeAnim, { - toValue: 1, - duration: 600, - delay: index * 100, - useNativeDriver: true, - }), - Animated.spring(scaleAnim, { - toValue: 1, - delay: index * 100, - tension: 40, - friction: 7, - useNativeDriver: true, - }), - ]).start(); - }, [fadeAnim, index, scaleAnim]); - + body: string; + isDesktop?: boolean; +}) { return ( - - - + + + {title} - {description} - - ); -}; - -/** - * StatsDisplay Component - * - * Section showing usage stats. - */ -const StatsDisplay = () => { - const { colors, fontScale } = useAppTheme(); - const styles = React.useMemo(() => createStyles(colors, fontScale), [colors, fontScale]); - const stats = [ - { value: '1,000+', label: 'Active Students' }, - { value: '500+', label: 'Events Monthly' }, - { value: '50+', label: 'Campus Clubs' }, - { value: '98%', label: 'Satisfaction' }, - ]; - - return ( - - {stats.map((stat, index) => ( - - ))} + {body} ); -}; +} -const AnimatedStat: React.FC<{ +function Stat({ + styles, + value, + label, +}: { + styles: ReturnType; value: string; label: string; - delay: number; -}> = ({ value, label, delay }) => { - const { colors, fontScale } = useAppTheme(); - const styles = React.useMemo(() => createStyles(colors, fontScale), [colors, fontScale]); - const scaleAnim = useRef(new Animated.Value(0)).current; - - useEffect(() => { - Animated.spring(scaleAnim, { - toValue: 1, - delay, - tension: 40, - friction: 7, - useNativeDriver: true, - }).start(); - }, [delay, scaleAnim]); - +}) { return ( - + {value} {label} - - ); -}; - -const FinalCTA = () => { - const { colors, fontScale } = useAppTheme(); - const styles = React.useMemo(() => createStyles(colors, fontScale), [colors, fontScale]); - return ( - - - Ready to transform your campus experience? - - - Join thousands of students already using CMUnify - - router.push('/(auth)/signup')} - activeOpacity={0.9} - > - Get Started Free - - - Made with โค๏ธ for students โ€ข ยฉ 2025 CMUnify - ); -}; +} + +/* โ”€โ”€โ”€ Styles โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ -/** - * Styles Definitions - * - * Modify colors, sizes, and layout here. - */ const createStyles = (colors: AppPalette, fontScale: number) => StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#E8E4F3', // Main background color - }, - scrollContent: { - flexGrow: 1, - }, - loadingContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: '#E8E4F3', - }, - loadingLogo: { - fontSize: 64, - }, - gradientBackground: { - ...StyleSheet.absoluteFillObject, - overflow: 'hidden', - backgroundColor: '#E8E4F3', - }, + container: { + flex: 1, + backgroundColor: colors.background, + }, + scrollContent: { + paddingBottom: 48, + }, + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: colors.background, + }, - // --- NEW BLOB ANIMATION STYLES --- - blobContainer: { - position: 'absolute', - top: -300, // Position partly off-screen to the top-left - left: -300, - width: 1000, - height: 1000, - justifyContent: 'center', - alignItems: 'center', - opacity: 0.6, // Make it subtle - }, - blobSpinner: { - width: 1000, - height: 1000, - justifyContent: 'center', - alignItems: 'center', - }, - blobPetal: { - position: 'absolute', - width: 300, - height: 900, // Long oval - backgroundColor: '#FF6B6B', // Brand Red/Pink - borderRadius: 500, // Fully rounded - }, - blobPetal1: { - // Vertical - opacity: 0.5, - backgroundColor: '#FF6B6B', - }, - blobPetal2: { - // Rotated 60 deg - transform: [{ rotate: '60deg' }], - opacity: 0.5, - backgroundColor: '#FF8E8E', - }, - blobPetal3: { - // Rotated -60 deg (or 120) - transform: [{ rotate: '-60deg' }], - opacity: 0.5, - backgroundColor: '#FF7A6B', - }, + /* Top bar */ + topBar: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 24, + paddingVertical: 18, + maxWidth: 1080, + width: '100%', + alignSelf: 'center', + }, + wordmark: { + fontSize: 20 * fontScale, + fontWeight: '800', + letterSpacing: -0.5, + color: colors.textPrimary, + }, + wordmarkAccent: { + color: colors.primary, + }, + topBarButton: { + paddingHorizontal: 16, + paddingVertical: 8, + borderRadius: 10, + borderWidth: 1, + borderColor: colors.border, + }, + topBarButtonText: { + fontSize: 14 * fontScale, + fontWeight: '600', + color: colors.textPrimary, + }, - hero: { - minHeight: height * 0.85, - justifyContent: 'center', - alignItems: 'center', - paddingHorizontal: 24, - paddingTop: 80, - paddingBottom: 60, - }, - heroContent: { - alignItems: 'center', - maxWidth: 900, - width: '100%', - }, - logoSection: { - marginBottom: 40, - }, - logoIcon: { - width: 100, - height: 100, - justifyContent: 'center', - alignItems: 'center', - }, - - // --- NEW LOGO STYLES --- - petalLogoContainer: { - width: 80, - height: 80, - position: 'relative', - justifyContent: 'center', - alignItems: 'center', - }, - petalEllipse: { - position: 'absolute', - width: 30, - height: 80, - borderRadius: 40, - backgroundColor: '#FF6B6B', - opacity: 0.9, - }, - petalVertical: { - // Default vertical - }, - petalRotated1: { - transform: [{ rotate: '60deg' }], - backgroundColor: '#FF6B6B', - }, - petalRotated2: { - transform: [{ rotate: '-60deg' }], - backgroundColor: '#FF6B6B', - }, + /* Hero */ + hero: { + alignItems: 'center', + paddingHorizontal: 24, + paddingTop: 72, + paddingBottom: 40, + maxWidth: 880, + width: '100%', + alignSelf: 'center', + }, + heroMobile: { + paddingTop: 40, + }, + announcePill: { + paddingHorizontal: 14, + paddingVertical: 6, + borderRadius: 999, + backgroundColor: colors.surfaceAlt, + borderWidth: 1, + borderColor: colors.border, + marginBottom: 28, + }, + announcePillText: { + fontSize: 13 * fontScale, + fontWeight: '500', + color: colors.textSecondary, + letterSpacing: 0.2, + }, + headline: { + fontSize: 64 * fontScale, + lineHeight: 68 * fontScale, + fontWeight: '800', + letterSpacing: -2, + textAlign: 'center', + color: colors.textPrimary, + marginBottom: 20, + }, + headlineMobile: { + fontSize: 42 * fontScale, + lineHeight: 46 * fontScale, + letterSpacing: -1.2, + }, + subheadline: { + fontSize: 18 * fontScale, + lineHeight: 28 * fontScale, + textAlign: 'center', + color: colors.textSecondary, + maxWidth: 620, + marginBottom: 32, + }, + subheadlineMobile: { + fontSize: 16 * fontScale, + lineHeight: 25 * fontScale, + }, + ctaRow: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 12, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 20, + }, + primaryCta: { + flexDirection: 'row', + alignItems: 'center', + gap: 8, + backgroundColor: colors.primary, + paddingHorizontal: 24, + paddingVertical: 14, + borderRadius: 12, + }, + primaryCtaText: { + fontSize: 16 * fontScale, + fontWeight: '600', + color: colors.onPrimary, + }, + secondaryCta: { + paddingHorizontal: 20, + paddingVertical: 14, + borderRadius: 12, + borderWidth: 1, + borderColor: colors.border, + backgroundColor: colors.surface, + }, + secondaryCtaText: { + fontSize: 15 * fontScale, + fontWeight: '600', + color: colors.textPrimary, + }, + trustLine: { + fontSize: 13 * fontScale, + color: colors.textTertiary, + textAlign: 'center', + }, + /* Product preview */ + previewSection: { + alignItems: 'center', + paddingVertical: 40, + paddingHorizontal: 24, + gap: 12, + maxWidth: 640, + width: '100%', + alignSelf: 'center', + }, + previewSectionMobile: { + paddingVertical: 24, + }, + previewCard: { + width: '100%', + maxWidth: 520, + flexDirection: 'row', + backgroundColor: colors.surface, + borderRadius: 14, + borderWidth: 1, + borderColor: colors.border, + overflow: 'hidden', + }, + previewCardElevated: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 8 }, + shadowOpacity: 0.08, + shadowRadius: 24, + elevation: 4, + transform: [{ scale: 1.03 }], + zIndex: 2, + }, + previewCardLeft: { + transform: [{ rotate: '-1.2deg' }, { translateX: -14 }], + opacity: 0.9, + }, + previewCardRight: { + transform: [{ rotate: '1.2deg' }, { translateX: 14 }], + opacity: 0.9, + }, + previewAccentBar: { + width: 4, + }, + previewCardBody: { + flex: 1, + padding: 16, + }, + previewCardTitle: { + fontSize: 16 * fontScale, + fontWeight: '700', + color: colors.textPrimary, + marginBottom: 4, + }, + previewCardMeta: { + fontSize: 13 * fontScale, + color: colors.textSecondary, + marginBottom: 12, + }, + previewCardFooter: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + previewChips: { + flexDirection: 'row', + gap: 6, + }, + previewChip: { + paddingHorizontal: 8, + paddingVertical: 3, + borderRadius: 6, + }, + previewChipText: { + fontSize: 11 * fontScale, + fontWeight: '600', + }, + previewGoing: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + previewGoingText: { + fontSize: 12 * fontScale, + color: colors.textSecondary, + fontWeight: '500', + }, - headline: { - fontSize: 72 * fontScale, - fontWeight: '800', - // The hero sits on the fixed light lavender/pink brand artwork, so its - // text must stay dark in every theme (theme tokens would go light in - // dark mode and vanish against the artwork) - color: '#1A1A2E', - textAlign: 'center', - marginBottom: 8, - letterSpacing: -2, - }, - headlineMobile: { - fontSize: 48 * fontScale, - }, - brandHeadline: { - fontSize: 72 * fontScale, - fontWeight: '800', - color: colors.primary, - textAlign: 'center', - marginBottom: 32, - letterSpacing: -2, - }, - brandHeadlineMobile: { - fontSize: 48 * fontScale, - }, - subheadline: { - fontSize: 22 * fontScale, - color: '#4B5563', // fixed: sits on the light hero artwork in every theme - textAlign: 'center', - marginBottom: 48, - lineHeight: 34, - maxWidth: 700, - }, - subheadlineMobile: { - fontSize: 18 * fontScale, - lineHeight: 28, - }, - ctaButtons: { - flexDirection: 'row', - gap: 16, - marginBottom: 48, - flexWrap: 'wrap', - justifyContent: 'center', - }, - primaryButton: { - backgroundColor: colors.primary, - paddingHorizontal: 40, - paddingVertical: 18, - borderRadius: 16, - minWidth: 180, - alignItems: 'center', - ...Platform.select({ - web: { - shadowColor: colors.primary, - shadowOffset: { width: 0, height: 12 }, - shadowOpacity: 0.4, - shadowRadius: 24, - }, - default: { - elevation: 12, - }, - }), - }, - primaryButtonText: { - color: colors.onPrimary, - fontSize: 18 * fontScale, - fontWeight: '700', - letterSpacing: 0.5, - }, - secondaryButton: { - backgroundColor: 'transparent', - paddingHorizontal: 40, - paddingVertical: 18, - borderRadius: 16, - minWidth: 180, - alignItems: 'center', - borderWidth: 2, - borderColor: '#1A1A2E', // fixed: sits on the light hero artwork in every theme - }, - secondaryButtonText: { - color: '#1A1A2E', // fixed: sits on the light hero artwork in every theme - fontSize: 18 * fontScale, - fontWeight: '600', - }, - trustIndicators: { - flexDirection: 'row', - gap: 32, - flexWrap: 'wrap', - justifyContent: 'center', - }, - trustItem: { - flexDirection: 'row', - alignItems: 'center', - gap: 8, - }, - trustIcon: { - color: colors.success, - fontSize: 18 * fontScale, - fontWeight: 'bold', - }, - trustText: { - color: '#4B5563', // fixed: sits on the light hero artwork in every theme - fontSize: 15 * fontScale, - fontWeight: '500', - }, - featuresSection: { - paddingVertical: 100, - paddingHorizontal: 24, - backgroundColor: colors.surface, - }, - sectionTitle: { - fontSize: 48 * fontScale, - fontWeight: '800', - color: colors.textPrimary, - textAlign: 'center', - marginBottom: 16, - letterSpacing: -1, - }, - sectionSubtitle: { - fontSize: 20 * fontScale, - color: colors.textSecondary, - textAlign: 'center', - marginBottom: 64, - }, - featuresGrid: { - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'center', - gap: 24, - maxWidth: 1200, - alignSelf: 'center', - }, - featuresGridDesktop: { - gap: 32, - }, - featureCard: { - width: width > 768 ? 360 : width - 48, - backgroundColor: colors.surface, - padding: 32, - borderRadius: 24, - borderWidth: 1, - borderColor: colors.border, - ...Platform.select({ - web: { - shadowColor: '#000', - shadowOffset: { width: 0, height: 4 }, - shadowOpacity: 0.06, - shadowRadius: 16, - }, - default: { - elevation: 3, - }, - }), - }, - featureIconWrapper: { - width: 72, - height: 72, - borderRadius: 18, - justifyContent: 'center', - alignItems: 'center', - marginBottom: 24, - }, - featureIcon: { - fontSize: 36, - }, - featureTitle: { - fontSize: 24 * fontScale, - fontWeight: '700', - color: colors.textPrimary, - marginBottom: 12, - }, - featureDescription: { - fontSize: 16 * fontScale, - color: colors.textSecondary, - lineHeight: 24, - }, - statsSection: { - paddingVertical: 100, - paddingHorizontal: 24, - backgroundColor: colors.surfaceAlt, - }, - statsContainer: { - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'center', - gap: 48, - maxWidth: 1000, - alignSelf: 'center', - }, - statCard: { - alignItems: 'center', - minWidth: 160, - }, - statValue: { - fontSize: 56 * fontScale, - fontWeight: '800', - color: colors.primary, - marginBottom: 8, - letterSpacing: -1, - }, - statLabel: { - fontSize: 16 * fontScale, - color: colors.textSecondary, - fontWeight: '500', - }, - ctaSection: { - paddingVertical: 120, - paddingHorizontal: 24, - backgroundColor: colors.surface, - }, - finalCTAContainer: { - alignItems: 'center', - maxWidth: 800, - alignSelf: 'center', - }, - finalCTATitle: { - fontSize: 48 * fontScale, - fontWeight: '800', - color: colors.textPrimary, - textAlign: 'center', - marginBottom: 20, - letterSpacing: -1, - }, - finalCTASubtitle: { - fontSize: 20 * fontScale, - color: colors.textSecondary, - textAlign: 'center', - marginBottom: 48, - }, - finalCTAButton: { - backgroundColor: colors.primary, - paddingHorizontal: 56, - paddingVertical: 22, - borderRadius: 16, - ...Platform.select({ - web: { - shadowColor: colors.primary, - shadowOffset: { width: 0, height: 12 }, - shadowOpacity: 0.4, - shadowRadius: 24, - }, - default: { - elevation: 12, - }, - }), - }, - finalCTAButtonText: { - color: colors.onPrimary, - fontSize: 20 * fontScale, - fontWeight: '700', - letterSpacing: 0.5, - }, - footer: { - marginTop: 48, - fontSize: 14 * fontScale, - color: colors.textTertiary, - textAlign: 'center', - }, -}); + /* Features */ + featuresSection: { + paddingHorizontal: 24, + paddingVertical: 56, + maxWidth: 1080, + width: '100%', + alignSelf: 'center', + }, + sectionKicker: { + fontSize: 12 * fontScale, + fontWeight: '700', + letterSpacing: 1.5, + color: colors.primary, + textAlign: 'center', + marginBottom: 10, + }, + sectionTitle: { + fontSize: 32 * fontScale, + fontWeight: '800', + letterSpacing: -0.8, + color: colors.textPrimary, + textAlign: 'center', + marginBottom: 36, + }, + featuresGrid: { + gap: 14, + }, + featuresGridDesktop: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'center', + }, + featureCard: { + backgroundColor: colors.surface, + borderRadius: 14, + borderWidth: 1, + borderColor: colors.border, + padding: 20, + }, + featureCardDesktop: { + flexBasis: 320, + flexGrow: 1, + maxWidth: 340, + }, + featureIconWrap: { + width: 38, + height: 38, + borderRadius: 10, + backgroundColor: colors.surfaceAlt, + justifyContent: 'center', + alignItems: 'center', + marginBottom: 14, + }, + featureTitle: { + fontSize: 16 * fontScale, + fontWeight: '700', + color: colors.textPrimary, + marginBottom: 6, + }, + featureBody: { + fontSize: 14 * fontScale, + lineHeight: 21 * fontScale, + color: colors.textSecondary, + }, + + /* Stats */ + statsStrip: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + gap: 8, + marginHorizontal: 24, + paddingVertical: 28, + borderTopWidth: 1, + borderBottomWidth: 1, + borderColor: colors.border, + maxWidth: 720, + width: 'auto', + alignSelf: 'center', + minWidth: '80%', + }, + stat: { + flex: 1, + alignItems: 'center', + }, + statValue: { + fontSize: 26 * fontScale, + fontWeight: '800', + letterSpacing: -0.5, + color: colors.textPrimary, + }, + statLabel: { + fontSize: 12 * fontScale, + color: colors.textTertiary, + marginTop: 2, + textAlign: 'center', + }, + statDivider: { + width: 1, + height: 36, + backgroundColor: colors.border, + }, + + /* Final CTA */ + finalCta: { + alignItems: 'center', + paddingHorizontal: 24, + paddingVertical: 64, + }, + finalCtaTitle: { + fontSize: 30 * fontScale, + fontWeight: '800', + letterSpacing: -0.8, + color: colors.textPrimary, + textAlign: 'center', + marginBottom: 10, + }, + finalCtaBody: { + fontSize: 16 * fontScale, + lineHeight: 24 * fontScale, + color: colors.textSecondary, + textAlign: 'center', + maxWidth: 480, + marginBottom: 24, + }, + + /* Footer */ + footer: { + alignItems: 'center', + paddingHorizontal: 24, + paddingTop: 8, + }, + footerText: { + fontSize: 13 * fontScale, + color: colors.textTertiary, + textAlign: 'center', + }, + }); diff --git a/apps/client/components/events/EventCard.tsx b/apps/client/components/events/EventCard.tsx index ecac2c5..53323db 100644 --- a/apps/client/components/events/EventCard.tsx +++ b/apps/client/components/events/EventCard.tsx @@ -105,7 +105,7 @@ export const EventCard: React.FC = ({ event, onPress, index = 0 )} {event.isSocialEvent && ( - Social + Social )} @@ -177,13 +177,15 @@ const createStyles = (colors: AppPalette, fontScale: number) => StyleSheet.create({ card: { backgroundColor: colors.surface, - borderRadius: 12, + borderRadius: 14, + borderWidth: 1, + borderColor: colors.border, overflow: 'hidden', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, - elevation: 3, + shadowOpacity: 0.04, + shadowRadius: 8, + elevation: 1, marginBottom: 16, }, colorBar: { @@ -209,19 +211,23 @@ const createStyles = (colors: AppPalette, fontScale: number) => color: colors.textPrimary, }, badge: { - backgroundColor: '#8B7FFF', + backgroundColor: 'rgba(139, 127, 255, 0.14)', paddingHorizontal: 8, paddingVertical: 4, - borderRadius: 4, + borderRadius: 6, }, socialBadge: { - backgroundColor: '#FF6BA8', + backgroundColor: 'rgba(255, 107, 168, 0.14)', + }, + socialBadgeText: { + color: '#E24E8C', }, badgeText: { fontSize: 10 * fontScale, - fontWeight: '600', - color: colors.onPrimary, + fontWeight: '700', + color: '#8B7FFF', textTransform: 'uppercase', + letterSpacing: 0.4, }, infoRow: { flexDirection: 'row', diff --git a/apps/client/components/ui/Button.tsx b/apps/client/components/ui/Button.tsx index 88f8b5f..d049706 100644 --- a/apps/client/components/ui/Button.tsx +++ b/apps/client/components/ui/Button.tsx @@ -86,7 +86,7 @@ const createStyles = (colors: AppPalette, fontScale: number) => flexDirection: 'row', justifyContent: 'center', alignItems: 'center', - borderRadius: 8, + borderRadius: 12, gap: 8, }, fullWidth: { @@ -101,8 +101,8 @@ const createStyles = (colors: AppPalette, fontScale: number) => }, outline: { backgroundColor: 'transparent', - borderWidth: 2, - borderColor: colors.primary, + borderWidth: 1, + borderColor: colors.border, }, ghost: { backgroundColor: 'transparent', @@ -131,7 +131,7 @@ const createStyles = (colors: AppPalette, fontScale: number) => color: colors.onPrimary, }, outlineText: { - color: colors.primary, + color: colors.textPrimary, }, ghostText: { color: colors.primary, diff --git a/apps/client/constants/theme.ts b/apps/client/constants/theme.ts index 22d49fb..ffdc4d7 100644 --- a/apps/client/constants/theme.ts +++ b/apps/client/constants/theme.ts @@ -65,65 +65,72 @@ export interface AppPalette { overlay: string; } +// Light: soft "unbleached" warm neutrals instead of pure white/cool gray โ€” +// easier on the eyes for content-heavy screens โ€” with a refined crimson +// accent reserved for actions and highlights (WCAG AA on light surfaces). const lightPalette: AppPalette = { - background: '#F8F9FA', + background: '#FAFAF9', surface: '#FFFFFF', - surfaceAlt: '#F3F4F6', - border: '#E5E7EB', - textPrimary: '#1F2937', - textSecondary: '#6B7280', - textTertiary: '#9CA3AF', - primary: '#FF6B6B', + surfaceAlt: '#F5F5F4', + border: '#E7E5E4', + textPrimary: '#1C1917', + textSecondary: '#57534E', + textTertiary: '#A8A29E', + primary: '#E11D48', onPrimary: '#FFFFFF', - success: '#6BCF7F', + success: '#059669', danger: '#DC2626', - dangerSoft: '#FEE2E2', + dangerSoft: '#FEF2F2', infoSoft: '#EFF6FF', - infoText: '#1E40AF', - overlay: 'rgba(0, 0, 0, 0.5)', + infoText: '#1D4ED8', + overlay: 'rgba(28, 25, 23, 0.5)', }; const lightHighContrastPalette: AppPalette = { ...lightPalette, background: '#FFFFFF', - surfaceAlt: '#EDEEF1', - border: '#6B7280', + surfaceAlt: '#EDECEA', + border: '#57534E', textPrimary: '#000000', - textSecondary: '#1F2937', - textTertiary: '#374151', - primary: '#D63333', + textSecondary: '#292524', + textTertiary: '#44403C', + primary: '#BE123C', danger: '#B91C1C', infoText: '#1E3A8A', }; +// Dark: stepped surfaces (~5-8% luminance per level) instead of pure black, +// desaturated text (not pure white) to avoid glare, and a more luminous +// accent โ€” colors need extra saturation/lightness on dark backgrounds. const darkPalette: AppPalette = { - background: '#111418', - surface: '#1C2127', - surfaceAlt: '#262C34', - border: '#3A424D', - textPrimary: '#F3F4F6', - textSecondary: '#B0B8C1', - textTertiary: '#7D8590', - primary: '#FF6B6B', - onPrimary: '#FFFFFF', - success: '#6BCF7F', + background: '#0C0E12', + surface: '#15181E', + surfaceAlt: '#1E222A', + border: '#2A2F38', + textPrimary: '#F4F4F5', + textSecondary: '#A1A1AA', + textTertiary: '#70737C', + primary: '#FB7185', + onPrimary: '#26060C', + success: '#34D399', danger: '#F87171', - dangerSoft: '#3F1D1D', - infoSoft: '#1E2A44', + dangerSoft: '#2D1517', + infoSoft: '#16233B', infoText: '#93C5FD', - overlay: 'rgba(0, 0, 0, 0.65)', + overlay: 'rgba(0, 0, 0, 0.6)', }; const darkHighContrastPalette: AppPalette = { ...darkPalette, background: '#000000', surface: '#101317', - surfaceAlt: '#1A1F26', - border: '#9CA3AF', + surfaceAlt: '#1A1E24', + border: '#8A8F98', textPrimary: '#FFFFFF', - textSecondary: '#E5E7EB', - textTertiary: '#C3C9D1', - primary: '#FF8585', + textSecondary: '#E4E4E7', + textTertiary: '#C3C7CD', + primary: '#FDA4AF', + onPrimary: '#26060C', }; export const AppPalettes = {