diff --git a/frontend/app/(tabs)/index.tsx b/frontend/app/(tabs)/index.tsx index 08752fa..96c0dba 100644 --- a/frontend/app/(tabs)/index.tsx +++ b/frontend/app/(tabs)/index.tsx @@ -336,6 +336,7 @@ export default function HomeScreen() { ) : null; const webRows = buildWebRows(listings, homeColumns); + const trustPoints = ['Verified students', 'Fast local pickup', 'No shipping hassle']; if (isWeb) { return ( @@ -377,14 +378,24 @@ export default function HomeScreen() { + Campus marketplace {hasSearchQuery ? `Results for "${searchQuery}"` : 'Latest listings'} {hasSearchQuery ? 'Use filters to narrow the results further.' - : 'Fresh campus listings from the PolyBuys marketplace.'} + : 'Buy and sell with Cal Poly students in a marketplace built for quick, trusted handoffs.'} + {!hasSearchQuery ? ( + + {trustPoints.map((point) => ( + + {point} + + ))} + + ) : null} [ @@ -480,7 +491,7 @@ export default function HomeScreen() { [styles.createChip, pressed && styles.createChipPressed]} @@ -493,6 +504,13 @@ export default function HomeScreen() { } /> + + {trustPoints.map((point) => ( + + {point} + + ))} + diff --git a/frontend/app/listings/[id].tsx b/frontend/app/listings/[id].tsx index afaa0d7..3ec7cc3 100644 --- a/frontend/app/listings/[id].tsx +++ b/frontend/app/listings/[id].tsx @@ -490,12 +490,14 @@ export default function ListingDetailScreen() { /> - Description - {listing.description} + + Description + {listing.description} + {sellerProfile && ( router.push(`/profile/${encodeURIComponent(listing.sellerId)}` as never)} accessibilityLabel={`View ${sellerProfile.name}'s profile`} accessibilityRole="button" @@ -596,17 +598,17 @@ const styles = StyleSheet.create({ width: '100%', maxWidth: 980, alignSelf: 'center', - paddingHorizontal: spacing.lg, + paddingHorizontal: spacing.xl, paddingTop: spacing.xl, - paddingBottom: spacing.xxl, + paddingBottom: spacing.xxl + spacing.sm, }, webBannerContainer: { - marginBottom: 12, + marginBottom: spacing.sm, backgroundColor: colors.infoBg, borderRadius: borderRadius.sm, borderWidth: 1, borderColor: colors.infoBorder, - padding: 12, + padding: spacing.md, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', @@ -614,7 +616,7 @@ const styles = StyleSheet.create({ }, webBannerTextWrap: { flex: 1, - gap: 2, + gap: spacing.xs, }, webBannerSubtext: { color: colors.infoText, @@ -643,12 +645,13 @@ const styles = StyleSheet.create({ borderRadius: borderRadius.lg, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, - padding: spacing.lg, - gap: spacing.md, + padding: spacing.xl, + gap: spacing.lg, overflow: 'hidden', + boxShadow: '0 16px 36px rgba(14, 107, 83, 0.08)', }, imageSection: { - marginBottom: spacing.lg, + gap: spacing.sm, }, heroImageWrap: { height: 280, @@ -664,7 +667,7 @@ const styles = StyleSheet.create({ imageIndicator: { alignItems: 'center', marginTop: spacing.sm, - paddingVertical: spacing.xs, + paddingVertical: spacing.sm, gap: spacing.xs, }, imageIndicatorText: { @@ -726,7 +729,6 @@ const styles = StyleSheet.create({ borderColor: colors.border, alignItems: 'center', justifyContent: 'center', - marginBottom: spacing.lg, }, placeholderText: { color: colors.text, @@ -736,8 +738,7 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between', - gap: spacing.md, - marginTop: spacing.xs, + gap: spacing.lg, }, title: { ...typography.title1, @@ -755,6 +756,7 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', gap: spacing.sm, + marginTop: spacing.xs, }, messageButton: { flex: 1, @@ -785,12 +787,13 @@ const styles = StyleSheet.create({ flexDirection: 'row', flexWrap: 'wrap', gap: spacing.sm, - marginTop: spacing.xs, + }, + sectionBlock: { + gap: spacing.sm, }, descriptionLabel: { ...typography.heading, color: colors.textDark, - marginTop: spacing.sm, }, description: { ...typography.body, @@ -800,13 +803,12 @@ const styles = StyleSheet.create({ sellerBlock: { flexDirection: 'row', alignItems: 'center', - backgroundColor: colors.surface, + backgroundColor: colors.surfaceBrand, borderRadius: borderRadius.md, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, padding: spacing.md, gap: spacing.md, - marginTop: spacing.sm, }, sellerAvatar: { width: 44, @@ -821,6 +823,7 @@ const styles = StyleSheet.create({ }, sellerInfo: { flex: 1, + gap: 2, }, sellerName: { ...typography.subhead, @@ -833,7 +836,6 @@ const styles = StyleSheet.create({ }, buttonContainer: { gap: spacing.sm, - marginTop: spacing.sm, }, markSoldButton: { backgroundColor: colors.primary, @@ -851,7 +853,7 @@ const styles = StyleSheet.create({ editButton: { borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, - backgroundColor: colors.white, + backgroundColor: colors.surfaceWarm, paddingVertical: spacing.md, borderRadius: borderRadius.md, alignItems: 'center', @@ -864,7 +866,7 @@ const styles = StyleSheet.create({ fontWeight: '600', }, reportLink: { - marginTop: spacing.md, + marginTop: spacing.xs, paddingVertical: spacing.sm, alignSelf: 'flex-start', }, diff --git a/frontend/app/profile/edit.tsx b/frontend/app/profile/edit.tsx index 9bae9a4..136d6b1 100644 --- a/frontend/app/profile/edit.tsx +++ b/frontend/app/profile/edit.tsx @@ -2,11 +2,13 @@ import { useEffect, useRef, useState } from 'react'; import { ActivityIndicator, Alert, + Platform, Pressable, StyleSheet, Text, TextInput, View, + useWindowDimensions, } from 'react-native'; import { useRouter } from 'expo-router'; import { useMutation, useQuery } from 'convex/react'; @@ -132,6 +134,8 @@ export default function ProfileEditScreen() { const [loadedKey, setLoadedKey] = useState(null); const { mappedUrls: pictureUrls } = useResolvedImageUrls(picture ? [picture] : []); const pictureUrl = pendingPictureUri ?? pictureUrls[0] ?? null; + const { width } = useWindowDimensions(); + const isCompactLayout = width < 420; useEffect(() => { if (!isAuthenticated || profile === undefined) return; @@ -323,117 +327,132 @@ export default function ProfileEditScreen() { return ( - - Profile picture - + + Profile picture + - [ - styles.pictureButton, - pressed && styles.buttonPressed, - isPreparingPicture && styles.buttonDisabled, - ]} - onPress={() => void handlePickPicture()} - disabled={isPreparingPicture || isSubmitting} - accessibilityRole="button" - accessibilityLabel={pictureUrl ? 'Change profile picture' : 'Add profile picture'} - > - {isPreparingPicture ? ( - - ) : ( - - {pictureUrl ? 'Change photo' : 'Upload photo'} - - )} - - {pictureUrl ? ( + + Add a clear square photo so buyers can recognize you quickly. + + [ - styles.removeButton, + styles.pictureButton, pressed && styles.buttonPressed, - (isPreparingPicture || isSubmitting) && styles.buttonDisabled, + isPreparingPicture && styles.buttonDisabled, ]} + onPress={() => void handlePickPicture()} + disabled={isPreparingPicture || isSubmitting} accessibilityRole="button" - accessibilityLabel="Remove profile picture" + accessibilityLabel={pictureUrl ? 'Change profile picture' : 'Add profile picture'} > - Remove photo + {isPreparingPicture ? ( + + ) : ( + + {pictureUrl ? 'Change photo' : 'Upload photo'} + + )} - ) : null} + {pictureUrl ? ( + [ + styles.removeButton, + pressed && styles.buttonPressed, + (isPreparingPicture || isSubmitting) && styles.buttonDisabled, + ]} + accessibilityRole="button" + accessibilityLabel="Remove profile picture" + > + Remove photo + + ) : null} + - - Name * - - - - Email * - - - - Bio - - - - Major * - + + + Basic information + + Name * + + + + Email * + + + + Bio + + - - Graduation year * - + + + School details + + Major * + + + + Graduation year * + + [ styles.saveButton, + isCompactLayout && styles.saveButtonCompact, pressed && styles.buttonPressed, (isSubmitting || isPreparingPicture) && styles.buttonDisabled, ]} @@ -465,30 +484,64 @@ const styles = StyleSheet.create({ }, content: { padding: spacing.xl, - gap: spacing.md, + gap: spacing.lg, paddingBottom: spacing.xxl, }, + sectionCard: { + backgroundColor: colors.white, + borderRadius: borderRadius.lg, + borderWidth: StyleSheet.hairlineWidth, + borderColor: colors.border, + padding: spacing.lg, + gap: spacing.md, + boxShadow: '0 14px 30px rgba(14, 107, 83, 0.06)', + }, + sectionCardBrand: { + backgroundColor: colors.surfaceBrand, + }, + sectionCardWarm: { + backgroundColor: colors.surfaceWarm, + }, + sectionTitle: { + ...typography.heading, + color: colors.textDark, + }, field: { - gap: spacing.xs, + gap: spacing.sm, }, pictureRow: { flexDirection: 'row', alignItems: 'center', - gap: spacing.md, + gap: spacing.lg, + }, + pictureRowCompact: { + flexDirection: 'column', + alignItems: 'flex-start', }, pictureActions: { flex: 1, - gap: spacing.xs, + gap: spacing.sm, + }, + pictureButtonRow: { + flexDirection: 'row', + flexWrap: 'wrap', + alignItems: 'center', + gap: spacing.sm, }, avatar: { width: 84, height: 84, borderRadius: 42, }, + helperText: { + ...typography.footnote, + color: colors.text, + maxWidth: 320, + }, pictureButton: { borderWidth: 1, borderColor: colors.border, - backgroundColor: colors.surface, + backgroundColor: colors.white, borderRadius: borderRadius.md, minHeight: 44, paddingHorizontal: spacing.md, @@ -526,10 +579,12 @@ const styles = StyleSheet.create({ ...typography.body, backgroundColor: colors.white, color: colors.textDark, + minHeight: 52, }, textArea: { - minHeight: 90, + minHeight: 112, textAlignVertical: 'top', + paddingTop: Platform.OS === 'ios' ? spacing.md : spacing.smPlus, }, saveButton: { backgroundColor: colors.primary, @@ -538,6 +593,9 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', minHeight: 48, + marginTop: spacing.xs, + }, + saveButtonCompact: { marginTop: spacing.sm, }, saveButtonText: { diff --git a/frontend/components/FilterBar.tsx b/frontend/components/FilterBar.tsx index cafa2c1..b1cda19 100644 --- a/frontend/components/FilterBar.tsx +++ b/frontend/components/FilterBar.tsx @@ -4,7 +4,7 @@ import { CATEGORY_LABELS, LISTING_SORT_SHORT } from '../types/filters'; import type { Category, Filters, ListingSortBy } from '../types/filters'; import { useEntranceAnimation } from '../hooks/useEntranceAnimation'; import { formatPrice } from '../lib/formatPrice'; -import { colors, borderRadius } from '../theme/tokens'; +import { colors, borderRadius, spacing, typography } from '../theme/tokens'; export type { Category, Filters }; @@ -150,7 +150,8 @@ const styles = StyleSheet.create({ scrollContent: { flexDirection: 'row', alignItems: 'center', - gap: 8, + gap: spacing.sm, + paddingVertical: 2, }, chip: { flexDirection: 'row', @@ -159,11 +160,11 @@ const styles = StyleSheet.create({ backgroundColor: colors.white, borderWidth: 1, borderColor: colors.border, - paddingVertical: 8, - paddingLeft: 14, - paddingRight: 10, + paddingVertical: spacing.sm, + paddingLeft: spacing.lg, + paddingRight: spacing.md, minHeight: 44, - gap: 6, + gap: spacing.xs, }, chipActive: { backgroundColor: colors.location, @@ -173,7 +174,7 @@ const styles = StyleSheet.create({ opacity: 0.9, }, chipText: { - fontSize: 14, + ...typography.footnoteMed, color: colors.text, fontWeight: '500', }, @@ -195,14 +196,14 @@ const styles = StyleSheet.create({ lineHeight: 14, }, clearAll: { - paddingHorizontal: 12, - paddingVertical: 8, + paddingHorizontal: spacing.md, + paddingVertical: spacing.sm, borderRadius: borderRadius.full, minHeight: 44, justifyContent: 'center', }, clearAllText: { - fontSize: 14, + ...typography.footnoteMed, color: colors.primary, fontWeight: '600', }, diff --git a/frontend/components/ListingCard.tsx b/frontend/components/ListingCard.tsx index abefedd..c18fe19 100644 --- a/frontend/components/ListingCard.tsx +++ b/frontend/components/ListingCard.tsx @@ -365,30 +365,30 @@ const LISTING_IMAGE_HEIGHT = 220; const styles = StyleSheet.create({ listingCardWrapper: { - marginBottom: spacing.md, + marginBottom: spacing.lg, flex: 1, minWidth: 0, }, listingCardWrapperHome: { - marginBottom: spacing.sm, + marginBottom: spacing.md, }, cardContainer: { position: 'relative', }, listingCard: { borderRadius: borderRadius.md, - backgroundColor: colors.surface, + backgroundColor: colors.white, overflow: 'hidden', borderWidth: 1, borderColor: colors.border, - boxShadow: '0 8px 20px rgba(21, 71, 52, 0.07)', + boxShadow: '0 12px 28px rgba(14, 107, 83, 0.09)', }, listingCardFlat: { backgroundColor: 'transparent', boxShadow: 'none', }, listingCardHover: { - boxShadow: '0 18px 36px rgba(21, 71, 52, 0.14)', + boxShadow: '0 22px 40px rgba(14, 107, 83, 0.16)', transform: [{ translateY: -2 }], }, listingCardPressed: { @@ -397,7 +397,7 @@ const styles = StyleSheet.create({ imageContainer: { width: '100%', height: LISTING_IMAGE_HEIGHT, - backgroundColor: colors.surface, + backgroundColor: colors.surfaceWarm, borderRadius: borderRadius.sm, overflow: 'hidden', position: 'relative', @@ -518,19 +518,19 @@ const styles = StyleSheet.create({ }, cardDetails: { paddingHorizontal: spacing.md, - paddingTop: spacing.sm, + paddingTop: spacing.md, paddingBottom: spacing.md, - gap: spacing.xs, + gap: spacing.sm, }, cardDetailsHome: { paddingHorizontal: spacing.smPlus, paddingTop: spacing.sm, paddingBottom: spacing.md, - gap: 2, + gap: spacing.xs, }, titleRow: { flexDirection: 'row', - alignItems: 'center', + alignItems: 'flex-start', gap: spacing.xs, }, listingTitle: { @@ -552,6 +552,7 @@ const styles = StyleSheet.create({ flexShrink: 0, color: colors.gray, fontWeight: '500', + marginTop: 1, }, listingConditionHome: { fontSize: 12, @@ -561,11 +562,10 @@ const styles = StyleSheet.create({ fontSize: 18, fontWeight: '700', color: colors.accent, - marginTop: spacing.xs, + letterSpacing: 0.28, }, listingPriceHome: { fontSize: 17, - marginTop: spacing.xs, }, footer: { marginTop: spacing.sm, diff --git a/frontend/components/ui/ScreenHeader.tsx b/frontend/components/ui/ScreenHeader.tsx index cc5cee5..a3cf650 100644 --- a/frontend/components/ui/ScreenHeader.tsx +++ b/frontend/components/ui/ScreenHeader.tsx @@ -36,12 +36,13 @@ export function ScreenHeader({ const styles = StyleSheet.create({ row: { flexDirection: 'row', - alignItems: 'center', + alignItems: 'flex-end', gap: spacing.md, }, copy: { flex: 1, minWidth: 0, + gap: spacing.xs, }, title: { ...typography.title1, @@ -50,10 +51,10 @@ const styles = StyleSheet.create({ subtitle: { ...typography.footnoteMed, color: colors.text, - marginTop: 2, }, actionSlot: { flexShrink: 0, + marginBottom: 2, }, }); diff --git a/frontend/theme/tokens.ts b/frontend/theme/tokens.ts index e2090a3..48587fc 100644 --- a/frontend/theme/tokens.ts +++ b/frontend/theme/tokens.ts @@ -5,55 +5,61 @@ export const colors = { /** Primary brand green - header, nav, primary buttons */ - primary: '#154734', + primary: '#0E6B53', /** Light page background */ - background: '#E8FCEF', + background: '#F2FBF4', /** Accent - prices, highlights */ - accent: '#E2A84A', + accent: '#F4A62A', /** Card/content surfaces */ - surface: '#F9FFFD', + surface: '#FBFFFD', + /** Brand-tinted surface for hero sections */ + surfaceBrand: '#E3F7ED', + /** Warm surface for supportive secondary sections */ + surfaceWarm: '#FFF4E1', + /** Soft coral tint for alerts and visual punctuation */ + surfaceCoral: '#FFE9E3', /** Body text */ - text: '#4A4A48', + text: '#41514A', /** Dark headings */ - textDark: '#1E1E1E', + textDark: '#11261F', /** Secondary borders, dividers */ - border: '#D9EBE4', + border: '#CCE7DB', /** Seller block, location chip background */ - muted: '#8DB1A3', + muted: '#739788', /** Category chip (e.g. Housing) */ - category: '#FF6E5E', + category: '#FF715B', /** Location chip background */ - location: '#ACDDCA', + location: '#C5F0D7', /** White for content areas */ white: '#FFFFFF', /** Light green for inactive nav */ - navInactive: '#D9EBE4', + navInactive: '#C4E5D7', /** Info banner (web app prompt) */ - infoBg: '#e8f4ff', - infoBorder: '#cae3ff', - infoText: '#1f4e80', - infoButton: '#1f6fb2', + infoBg: '#E6F5FF', + infoBorder: '#B8DFFF', + infoText: '#17537F', + infoButton: '#1178C8', /** Error/destructive states */ - errorBg: '#fff0f0', - errorBorder: '#f6cdcd', - errorText: '#ad2020', - destructive: '#b3261e', + errorBg: '#FFF1F0', + errorBorder: '#F7C7C2', + errorText: '#B93024', + destructive: '#C0392B', /** Neutral grays for placeholders, secondary text */ - gray: '#666666', - grayLight: '#eaeaea', + gray: '#66756E', + grayLight: '#E7EFEA', /** Shadow */ shadow: '#000000', /** Warning/alert banner (e.g. hidden listing) */ - warningBg: '#fff9eb', - warningBorder: '#f0d37a', - warningText: '#6c4f20', - warningTextMuted: '#8d6e0a', - warningLink: '#8a5a00', - locationDark: '#6ea48e', - overlayLight: 'rgba(0,0,0,0.08)', + warningBg: '#FFF4DD', + warningBorder: '#F2C66B', + warningText: '#7A4D12', + warningTextMuted: '#9A6C1F', + warningLink: '#A25D00', + locationDark: '#1E8A67', + overlayLight: 'rgba(14, 107, 83, 0.12)', /** Placeholder/empty state surfaces */ - placeholderBg: '#f3f7f5', - placeholderBorder: '#d8e6df', + placeholderBg: '#EEF7F2', + placeholderBorder: '#D4E5DD', } as const; export const spacing = {