From 9949b85001bb4acd9277c0cf50514b7a14c60833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Nu=C3=B1ez=20Romeu?= Date: Tue, 7 Jul 2026 18:08:44 +0200 Subject: [PATCH] Refactor frontend-mobile --- apps/frontend-mobile/app/(tabs)/_layout.tsx | 92 +++++- apps/frontend-mobile/app/(tabs)/alerts.tsx | 284 ++++++++++++++++++ apps/frontend-mobile/app/(tabs)/home.tsx | 137 +++++++++ apps/frontend-mobile/app/(tabs)/index.tsx | 29 +- apps/frontend-mobile/app/(tabs)/map.tsx | 12 +- apps/frontend-mobile/app/(tabs)/profile.tsx | 72 ++--- apps/frontend-mobile/app/(tabs)/scan.tsx | 81 +++++ apps/frontend-mobile/app/_layout.tsx | 22 +- apps/frontend-mobile/app/medicine/[id].tsx | 17 +- .../frontend-mobile/assets/farmaclic_logo.png | Bin 0 -> 9779 bytes .../components/MedicineCard.tsx | 10 +- apps/frontend-mobile/components/SearchBar.tsx | 11 +- .../frontend-mobile/components/StockBadge.tsx | 6 +- apps/frontend-mobile/constants/theme.ts | 98 ++++-- 14 files changed, 771 insertions(+), 100 deletions(-) create mode 100644 apps/frontend-mobile/app/(tabs)/alerts.tsx create mode 100644 apps/frontend-mobile/app/(tabs)/home.tsx create mode 100644 apps/frontend-mobile/app/(tabs)/scan.tsx create mode 100644 apps/frontend-mobile/assets/farmaclic_logo.png diff --git a/apps/frontend-mobile/app/(tabs)/_layout.tsx b/apps/frontend-mobile/app/(tabs)/_layout.tsx index f95d92c..fc4334c 100644 --- a/apps/frontend-mobile/app/(tabs)/_layout.tsx +++ b/apps/frontend-mobile/app/(tabs)/_layout.tsx @@ -1,14 +1,40 @@ import { Tabs } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; +import { View, StyleSheet, Platform } from 'react-native'; +import { colors, shadows } from '../../constants/theme'; + +function ScanIcon({ color, size }: { color: string; size: number }) { + return ( + + + + + + ); +} export default function TabLayout() { return ( + ( + + ), + }} + /> + , + tabBarLabel: () => null, + }} + /> + ( + + ), + }} + /> ( - - ), + href: null, }} /> ); } + +const styles = StyleSheet.create({ + tabBar: { + backgroundColor: colors.card, + borderTopColor: colors.border, + height: Platform.OS === 'ios' ? 88 : 64, + paddingBottom: Platform.OS === 'ios' ? 24 : 8, + paddingTop: 8, + }, + tabBarLabel: { + fontSize: 11, + fontWeight: '500', + }, + header: { + backgroundColor: colors.card, + shadowColor: '#000', + shadowOffset: { width: 0, height: 1 }, + shadowOpacity: 0.05, + shadowRadius: 4, + elevation: 1, + }, + headerTitle: { + color: colors.text, + fontWeight: '600', + fontSize: 17, + }, + fabContainer: { + position: 'absolute', + top: -16, + alignItems: 'center', + }, + fab: { + width: 52, + height: 52, + borderRadius: 26, + backgroundColor: colors.scanButton, + alignItems: 'center', + justifyContent: 'center', + }, +}); diff --git a/apps/frontend-mobile/app/(tabs)/alerts.tsx b/apps/frontend-mobile/app/(tabs)/alerts.tsx new file mode 100644 index 0000000..0b9b7ea --- /dev/null +++ b/apps/frontend-mobile/app/(tabs)/alerts.tsx @@ -0,0 +1,284 @@ +import React, { useEffect, useState } from 'react'; +import { View, Text, StyleSheet, FlatList, TouchableOpacity, Alert } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { colors, spacing, borderRadius, shadows } from '../../constants/theme'; +import { LoadingSpinner } from '../../components/LoadingSpinner'; + +interface NotificationItem { + scope: string; + id: number; + medicine_name?: string; + medicine_nregistro?: string; + pharmacy_name?: string; + pharmacy_id?: number; + pharmacy_address?: string; + created_at?: string; +} + +export default function AlertsScreen() { + const [items, setItems] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [deletingId, setDeletingId] = useState(null); + + useEffect(() => { + loadNotifications(); + }, []); + + async function loadNotifications() { + setIsLoading(true); + setError(null); + try { + const res = await fetch('/api/notifications/mine', { credentials: 'include' }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const data = await res.json(); + const merged = [ + ...(data.pharmacy || []), + ...(data.global || []), + ].sort((a: NotificationItem, b: NotificationItem) => + (b.created_at || '').localeCompare(a.created_at || '') + ); + setItems(merged); + } catch (err: any) { + setError(err.message || 'No se pudieron cargar las notificaciones'); + } finally { + setIsLoading(false); + } + } + + async function handleDelete(item: NotificationItem) { + const key = `${item.scope}:${item.id}`; + Alert.alert( + 'Eliminar notificación', + `¿Eliminar la notificación de ${item.medicine_name || item.medicine_nregistro}?`, + [ + { text: 'Cancelar', style: 'cancel' }, + { + text: 'Eliminar', + style: 'destructive', + onPress: async () => { + setDeletingId(key); + try { + const res = await fetch('/api/notifications/mine', { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + body: JSON.stringify({ scope: item.scope, id: item.id }), + }); + if (!res.ok && res.status !== 204) throw new Error(`HTTP ${res.status}`); + setItems(prev => prev.filter(i => !(i.scope === item.scope && i.id === item.id))); + } catch (err: any) { + Alert.alert('Error', err.message || 'No se pudo eliminar'); + } finally { + setDeletingId(null); + } + }, + }, + ] + ); + } + + function renderItem({ item }: { item: NotificationItem }) { + const key = `${item.scope}:${item.id}`; + return ( + + + + {item.medicine_name || item.medicine_nregistro} + + + + + + {item.scope === 'pharmacy' + ? item.pharmacy_name || `Farmacia #${item.pharmacy_id}` + : 'Cualquier farmacia'} + + + {item.pharmacy_address && ( + + {item.pharmacy_address} + + )} + + + handleDelete(item)} + disabled={deletingId === key} + > + {deletingId === key ? ( + + ) : ( + + )} + + + ); + } + + if (isLoading) { + return ; + } + + return ( + + + Notificaciones Guardadas + + Recibe avisos cuando medicamentos sin stock se repongan + + + + {error && ( + + {error} + + Reintentar + + + )} + + {!error && items.length === 0 && ( + + + Sin notificaciones + + Toca la campana en una farmacia sin stock para recibir notificaciones cuando se reponga. + + + )} + + {!error && items.length > 0 && ( + `${item.scope}:${item.id}`} + renderItem={renderItem} + contentContainerStyle={styles.list} + showsVerticalScrollIndicator={false} + /> + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + header: { + paddingHorizontal: spacing.lg, + paddingTop: spacing.lg, + paddingBottom: spacing.md, + }, + title: { + fontSize: 22, + fontWeight: 'bold', + color: colors.text, + }, + subtitle: { + fontSize: 14, + color: colors.textSecondary, + marginTop: spacing.xs, + lineHeight: 20, + }, + list: { + paddingHorizontal: spacing.lg, + paddingBottom: spacing.xl, + gap: spacing.sm, + }, + item: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: colors.card, + borderRadius: borderRadius.lg, + padding: spacing.md, + ...shadows.card, + }, + itemContent: { + flex: 1, + gap: spacing.xs, + }, + itemName: { + fontSize: 16, + fontWeight: '600', + color: colors.text, + lineHeight: 22, + }, + itemMeta: { + gap: spacing.xs, + }, + chip: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.xs, + backgroundColor: colors.primaryContainer, + borderRadius: borderRadius.full, + paddingHorizontal: spacing.sm, + paddingVertical: 3, + alignSelf: 'flex-start', + }, + chipText: { + fontSize: 12, + fontWeight: '600', + color: colors.onPrimaryContainer, + }, + itemAddress: { + fontSize: 12, + color: colors.textSecondary, + }, + deleteButton: { + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: colors.dangerContainer, + alignItems: 'center', + justifyContent: 'center', + marginLeft: spacing.sm, + }, + errorContainer: { + margin: spacing.lg, + padding: spacing.md, + backgroundColor: colors.dangerContainer, + borderRadius: borderRadius.lg, + alignItems: 'center', + gap: spacing.sm, + }, + errorText: { + fontSize: 14, + color: colors.danger, + textAlign: 'center', + }, + retryButton: { + paddingHorizontal: spacing.md, + paddingVertical: spacing.xs, + }, + retryText: { + fontSize: 14, + fontWeight: '600', + color: colors.primary, + }, + emptyContainer: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: spacing.xl, + gap: spacing.md, + }, + emptyTitle: { + fontSize: 18, + fontWeight: '600', + color: colors.text, + }, + emptyText: { + fontSize: 14, + color: colors.textSecondary, + textAlign: 'center', + lineHeight: 20, + }, +}); diff --git a/apps/frontend-mobile/app/(tabs)/home.tsx b/apps/frontend-mobile/app/(tabs)/home.tsx new file mode 100644 index 0000000..d9f0c97 --- /dev/null +++ b/apps/frontend-mobile/app/(tabs)/home.tsx @@ -0,0 +1,137 @@ +import React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity, Image } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { useRouter } from 'expo-router'; +import { colors, spacing, borderRadius, shadows } from '../../constants/theme'; + +export default function HomeScreen() { + const router = useRouter(); + + return ( + + + + FarmaClic + + Encuentra tus medicamentos en farmacias cercanas + + + + + router.push('/(tabs)')} + > + + + + + Buscar Medicamento + + + + + router.push('/scanner')} + > + + + + + Escanear TSI + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + alignItems: 'center', + justifyContent: 'center', + paddingHorizontal: spacing.lg, + gap: spacing.lg, + }, + hero: { + alignItems: 'center', + gap: spacing.sm, + marginBottom: spacing.md, + }, + logo: { + width: 120, + height: 120, + marginBottom: spacing.xs, + }, + brandName: { + fontSize: 28, + fontWeight: 'bold', + color: colors.text, + letterSpacing: -0.5, + }, + description: { + fontSize: 16, + color: colors.textSecondary, + textAlign: 'center', + maxWidth: 260, + lineHeight: 24, + }, + cards: { + width: '100%', + maxWidth: 320, + gap: spacing.md, + }, + card: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.md, + backgroundColor: colors.primaryContainer, + borderRadius: borderRadius.lg, + padding: spacing.md, + minHeight: 64, + ...shadows.card, + }, + cardScan: { + backgroundColor: colors.scanButton, + }, + cardIcon: { + width: 48, + height: 48, + borderRadius: borderRadius.md, + backgroundColor: 'rgba(255, 255, 255, 0.2)', + alignItems: 'center', + justifyContent: 'center', + flexShrink: 0, + }, + cardIconSearch: { + backgroundColor: 'rgba(255, 255, 255, 0.3)', + }, + cardIconScan: { + backgroundColor: 'rgba(255, 255, 255, 0.2)', + }, + cardContent: { + flex: 1, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + cardLabel: { + fontSize: 18, + fontWeight: '700', + color: colors.onPrimaryContainer, + lineHeight: 24, + }, + cardLabelScan: { + color: '#ffffff', + }, +}); diff --git a/apps/frontend-mobile/app/(tabs)/index.tsx b/apps/frontend-mobile/app/(tabs)/index.tsx index 0db711b..ebe19f3 100644 --- a/apps/frontend-mobile/app/(tabs)/index.tsx +++ b/apps/frontend-mobile/app/(tabs)/index.tsx @@ -55,11 +55,11 @@ export default function HomeScreen() { value={query} onChangeText={setQuery} /> - router.push('/scanner')} > - + @@ -96,25 +96,34 @@ const styles = StyleSheet.create({ searchContainer: { flexDirection: 'row', alignItems: 'center', + paddingRight: spacing.lg, }, scannerButton: { - marginRight: spacing.md, - padding: spacing.sm, - backgroundColor: colors.card, - borderRadius: borderRadius.md, + width: 44, + height: 44, + borderRadius: 22, + backgroundColor: colors.scanButton, + alignItems: 'center', + justifyContent: 'center', + shadowColor: '#2b5bb5', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 12, + elevation: 6, }, list: { paddingBottom: spacing.xl, }, errorContainer: { padding: spacing.md, - marginHorizontal: spacing.md, - backgroundColor: '#F8D7DA', - borderRadius: 8, + marginHorizontal: spacing.lg, + backgroundColor: colors.dangerContainer, + borderRadius: borderRadius.lg, }, errorText: { - color: '#721C24', + color: colors.danger, textAlign: 'center', + fontSize: 14, }, emptyContainer: { padding: spacing.xl, diff --git a/apps/frontend-mobile/app/(tabs)/map.tsx b/apps/frontend-mobile/app/(tabs)/map.tsx index e663741..33c49ae 100644 --- a/apps/frontend-mobile/app/(tabs)/map.tsx +++ b/apps/frontend-mobile/app/(tabs)/map.tsx @@ -92,14 +92,14 @@ const styles = StyleSheet.create({ left: spacing.md, right: spacing.md, backgroundColor: colors.card, - borderRadius: 8, - padding: spacing.sm, + borderRadius: borderRadius.lg, + padding: spacing.sm + 4, alignItems: 'center', shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.25, - shadowRadius: 4, - elevation: 5, + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.08, + shadowRadius: 20, + elevation: 4, }, legendText: { fontSize: 14, diff --git a/apps/frontend-mobile/app/(tabs)/profile.tsx b/apps/frontend-mobile/app/(tabs)/profile.tsx index afb33ae..5d6c3c6 100644 --- a/apps/frontend-mobile/app/(tabs)/profile.tsx +++ b/apps/frontend-mobile/app/(tabs)/profile.tsx @@ -830,7 +830,7 @@ const styles = StyleSheet.create({ width: 100, height: 100, borderRadius: 50, - backgroundColor: colors.background, + backgroundColor: colors.primaryContainer, justifyContent: 'center', alignItems: 'center', overflow: 'hidden', @@ -846,7 +846,7 @@ const styles = StyleSheet.create({ left: 0, right: 0, bottom: 0, - backgroundColor: 'rgba(0, 0, 0, 0.5)', + backgroundColor: 'rgba(0, 0, 0, 0.4)', justifyContent: 'center', alignItems: 'center', opacity: 0.8, @@ -857,17 +857,17 @@ const styles = StyleSheet.create({ fontSize: 14, }, infoSection: { - padding: spacing.xl, + padding: spacing.lg, backgroundColor: colors.card, - marginTop: spacing.md, + marginTop: spacing.sm, gap: spacing.md, }, infoCard: { - backgroundColor: colors.background, + backgroundColor: colors.surfaceLow, padding: spacing.md, - borderRadius: borderRadius.md, + borderRadius: borderRadius.lg, borderWidth: 1, - borderColor: colors.border || '#E0E0E0', + borderColor: colors.border, }, infoLabel: { fontSize: 12, @@ -883,7 +883,7 @@ const styles = StyleSheet.create({ color: colors.text, }, menuSection: { - marginTop: spacing.md, + marginTop: spacing.sm, backgroundColor: colors.card, }, menuItem: { @@ -891,7 +891,7 @@ const styles = StyleSheet.create({ alignItems: 'center', padding: spacing.md, borderBottomWidth: 1, - borderBottomColor: colors.separator, + borderBottomColor: colors.border, }, menuText: { flex: 1, @@ -901,9 +901,9 @@ const styles = StyleSheet.create({ }, searchHistorySection: { padding: spacing.md, - backgroundColor: colors.background, + backgroundColor: colors.surfaceLow, borderBottomWidth: 1, - borderBottomColor: colors.separator, + borderBottomColor: colors.border, }, searchHistoryTitle: { fontSize: 14, @@ -916,7 +916,7 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', paddingVertical: spacing.sm, borderBottomWidth: 1, - borderBottomColor: colors.separator, + borderBottomColor: colors.border, }, searchAddress: { flex: 1, @@ -928,7 +928,7 @@ const styles = StyleSheet.create({ }, button: { backgroundColor: colors.primary, - borderRadius: borderRadius.md, + borderRadius: borderRadius.lg, paddingVertical: spacing.md, paddingHorizontal: spacing.xl, }, @@ -951,7 +951,7 @@ const styles = StyleSheet.create({ margin: spacing.xl, padding: spacing.md, backgroundColor: colors.card, - borderRadius: borderRadius.md, + borderRadius: borderRadius.lg, borderWidth: 1, borderColor: colors.danger, }, @@ -979,7 +979,7 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', padding: spacing.lg, borderBottomWidth: 1, - borderBottomColor: colors.separator, + borderBottomColor: colors.border, }, modalTitle: { fontSize: 18, @@ -1000,26 +1000,26 @@ const styles = StyleSheet.create({ }, modalInput: { borderWidth: 1, - borderColor: colors.border || '#E0E0E0', - borderRadius: borderRadius.md, + borderColor: colors.border, + borderRadius: borderRadius.lg, padding: spacing.md, fontSize: 16, color: colors.text, }, modalFeedback: { padding: spacing.md, - borderRadius: borderRadius.md, + borderRadius: borderRadius.lg, marginBottom: spacing.md, }, modalFeedbackOk: { - backgroundColor: 'rgba(0, 69, 13, 0.1)', + backgroundColor: '#eaf7ec', borderWidth: 1, - borderColor: 'rgba(0, 69, 13, 0.2)', + borderColor: '#cfead0', }, modalFeedbackErr: { - backgroundColor: 'rgba(186, 26, 26, 0.1)', + backgroundColor: colors.dangerContainer, borderWidth: 1, - borderColor: 'rgba(186, 26, 26, 0.2)', + borderColor: '#fecaca', }, modalFeedbackText: { fontSize: 14, @@ -1034,9 +1034,9 @@ const styles = StyleSheet.create({ modalCancelBtn: { paddingVertical: spacing.md, paddingHorizontal: spacing.lg, - borderRadius: borderRadius.md, + borderRadius: borderRadius.lg, borderWidth: 1, - borderColor: colors.border || '#E0E0E0', + borderColor: colors.border, }, modalCancelText: { fontSize: 16, @@ -1046,7 +1046,7 @@ const styles = StyleSheet.create({ backgroundColor: colors.primary, paddingVertical: spacing.md, paddingHorizontal: spacing.xl, - borderRadius: borderRadius.md, + borderRadius: borderRadius.lg, minWidth: 100, alignItems: 'center', }, @@ -1068,7 +1068,7 @@ const styles = StyleSheet.create({ tabContainer: { flexDirection: 'row', borderBottomWidth: 1, - borderBottomColor: colors.separator, + borderBottomColor: colors.border, }, tabButton: { flex: 1, @@ -1115,10 +1115,10 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', padding: spacing.md, - backgroundColor: colors.background, - borderRadius: borderRadius.md, + backgroundColor: colors.surfaceLow, + borderRadius: borderRadius.lg, borderWidth: 1, - borderColor: colors.border || '#E5E5EA', + borderColor: colors.border, }, uploadIconContainer: { width: 56, @@ -1155,14 +1155,14 @@ const styles = StyleSheet.create({ alignItems: 'flex-start', justifyContent: 'space-between', padding: spacing.md, - backgroundColor: colors.background, - borderRadius: borderRadius.md, + backgroundColor: colors.surfaceLow, + borderRadius: borderRadius.lg, borderWidth: 1, - borderColor: colors.border || '#E0E0E0', + borderColor: colors.border, }, addressItemDefault: { borderColor: colors.primary, - backgroundColor: 'rgba(0, 69, 13, 0.03)', + backgroundColor: '#eaf7ec', }, addressItemInfo: { flex: 1, @@ -1181,7 +1181,7 @@ const styles = StyleSheet.create({ addressBadge: { alignSelf: 'flex-start', backgroundColor: colors.primary, - borderRadius: 12, + borderRadius: borderRadius.full, paddingHorizontal: spacing.sm, paddingVertical: 2, marginTop: 4, @@ -1219,9 +1219,9 @@ const styles = StyleSheet.create({ padding: spacing.md, marginTop: spacing.md, borderWidth: 2, - borderColor: colors.border || '#E0E0E0', + borderColor: colors.border, borderStyle: 'dashed', - borderRadius: borderRadius.md, + borderRadius: borderRadius.lg, }, addressAddBtnText: { fontSize: 15, diff --git a/apps/frontend-mobile/app/(tabs)/scan.tsx b/apps/frontend-mobile/app/(tabs)/scan.tsx new file mode 100644 index 0000000..831aced --- /dev/null +++ b/apps/frontend-mobile/app/(tabs)/scan.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; +import { Ionicons } from '@expo/vector-icons'; +import { useRouter } from 'expo-router'; +import { colors, spacing, borderRadius, shadows } from '../../constants/theme'; + +export default function ScanTabScreen() { + const router = useRouter(); + + return ( + + + + + + Escanear TSI + + Escanea el código de barras de tu tarjeta sanitaria para encontrar tus medicamentos + + router.push('/scanner')} + > + + Iniciar escaneo + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + alignItems: 'center', + justifyContent: 'center', + }, + content: { + alignItems: 'center', + paddingHorizontal: spacing.xl, + gap: spacing.md, + }, + iconContainer: { + width: 100, + height: 100, + borderRadius: 50, + backgroundColor: colors.primaryContainer, + alignItems: 'center', + justifyContent: 'center', + marginBottom: spacing.sm, + }, + title: { + fontSize: 22, + fontWeight: 'bold', + color: colors.text, + }, + description: { + fontSize: 15, + color: colors.textSecondary, + textAlign: 'center', + lineHeight: 22, + maxWidth: 280, + }, + button: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.sm, + backgroundColor: colors.scanButton, + borderRadius: borderRadius.lg, + paddingVertical: spacing.md, + paddingHorizontal: spacing.xl, + marginTop: spacing.md, + }, + buttonText: { + fontSize: 17, + fontWeight: '600', + color: '#ffffff', + }, +}); diff --git a/apps/frontend-mobile/app/_layout.tsx b/apps/frontend-mobile/app/_layout.tsx index b8039f4..77f4f42 100644 --- a/apps/frontend-mobile/app/_layout.tsx +++ b/apps/frontend-mobile/app/_layout.tsx @@ -5,6 +5,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { useAuthStore } from '../store/authStore'; import { registerForPushNotifications, addNotificationListener, addNotificationResponseListener } from '../services/notifications'; +import { colors } from '../constants/theme'; const queryClient = new QueryClient(); @@ -35,20 +36,27 @@ export default function RootLayout() { return ( - + + h001BWNkl|zi_c|oNJHb8v^L8GXMiXf;6f}m813Wy4d zNK`;U5T%LIA&`)SB$s-3yEF6qJbSskh;-ZU`v>>Q?cVIncb-1aLg3#A!6_)EfDoeo z{pmn-LjMwI&GW?}&u0K2k)zsg{ssIiq-14f(X6a2Ts&NL_f?)ioyKNMU%lekcQs`F zYsCDMq@ZNP7OF0}`?}K;JJcCg2~`)yCwbC5pdav6nUl87+w#JqMT^2pDe}xS&yc5| zep>z0YW25~f{Q|AWMt5soE-bm@2`FIuCpr|+Kb}?X5#7fn?QMP@mpo(r76TuZ;8`= zi}?X{bD%h2#U!UjvXE5#J0?wk8~zSba7?rfwL~Dg4C&LVWZSm#rE{0=zwO-ze%fDI z)b^SVXH}b$y4J1SaLMdP-&@{mbf1SR1C_7wN;+uPfaznd>3!=tAuF^uXWD1^wF5=R zMv42|$oWf2$;`}T+1c5(hV%Nz?>rac^lPg;*42z^=Xwf4R}il6|BaUYdT#qx!mCxP zU}YJvUR~8a-Atc@lF@T|?;T}DrIjBI%x;tUa*z31s@BOWx4*GUt3DMwLyH$qTDYfX zc$6H(Urx?nLQ0Kz960&R;HbQm3!QZ&S~0-pDEp?j@S8ak;##`@%6F&CSTjfP|YGPDxDlcAL^|g3)tK|7S}Y zlnn!=!jvfv5;!OYG)$%2cFpzf*GEoxa>DwWdBkkapT;SZ5;LSYkv5r^vG5Wq|OH(U`Q$j4l2&X`mBtj|xMkFCV<_+lu?A74bqUW;5WLF=$ zL4O9PM9QIY#yO96d5EUDU#Kv`9s$zk$dn?K0s#UFhyX>XBt|7A4I-{IFcPm@t@7VY zNH8n_o9UovD)D|{hWVDtsuD>O$+eoz>wkFfd%iBCdOcT}8hO&~ViskBBOQ*85(0!M zK>(NrI`M~mYeMTw?wc`c#?OBRF{eU`Bc?arbE7ZPI%RUMQQkGITR;`Hff5!CQwe|+ zpeY3&jVX^#iB`=?;{*OiL6>=54$YlF4X{iOT&Dmg0itUJ3>ODR6~vX9g&*8Ixz1Tn z-aM)>jy@je<5rSK=W!+lP6bGU`+;)9lmaCvfaVRm%Ol&ueP%s1ZO)&yGbcq#OhP)` zkdb6|YWiuu5jkjVWJqx&q{AIIBzezI{cM*kJO?<5P)0OLDNu?D zU6hP~PX{-nAf*H%q*MeTQfiYkOPe$(VC>3|$~H_R9%kc04bH&e1WX2CY9J{A7N|1w z7WRL)UhAPZkJ}eu_bU#9aH8WtTnR|=aA=(11`h?0mW@gZN`g{UNSAc+(^#_+&vtq8 zsSL>AIq<*0@kDWAq-14L_4L!~qI+-hTBpT-m9JNvt#oElqBstKLdwC0)C3mT_M3qWX;&_D_MWy#~STDN@Y zy6J`W%)6AAg92p}pcL(s6aqX%LeqG+Ly&Dp0C$8Ce5?{sNwlQWxFM4te)GkQj0}Eg zM13MSE-C0`x)MU?DkX26I%wY7f*m)3TZ>RCH44m;1~&+37%;(aa*z@wmMXkNCFLe{ z+m*T13o14hPTRg^^Ovd3TDq-v4Yq8xOOmOkSp+24&?o>?x<(@Ih;TI#{^u7yJbBm! zcU|2)@uF5;HWuXF6OPkQ3sDWJrh`T}eo$U99X31&R)V0yrj#-?Dbfl<1D_5}Q%qpe0`z(klq5T+^oS_#HZ-s61&@x&h%~V%QWa@00f@=9GIw$Hyaok)(C1UKiw~I_{?-xytNUj* zFRH7L&1KcsgnctFVrU_hWi3Mwd1tO_hG`y}t`?CfmZv=j13 zME^?5eqbc&vfhHjCMY3$l8?lxJFxmGFpQ zD6&ct83{r2Bo8!9G(ZWA4ge?vn}xZSApL#bx?}v?C#ptAB_%eozEiRWEj+k>v~Y8a zfa3TIDg-o6bU})TfwToEsss_hUDfWN$tKl#&P(s?M;Cc`ru=7tQdaXRFBq2jSgwb? z6x1zCXOv5#fQbmSt)BpiXtAY@mQA8ROOOfsJ)3EpF;9;P;_r_qEk`5;r362__wOFR zy|r)iws27$u2ba%A|RoTn?SQV9*~v=L`q44F;W3r)vr~ZK6>G!JjehJIY%Ufpxx;= zcHk}hB1Pvb#Wa`Ozq3G*{may0vQ<`a&qiD#tk04v|`6A z%FAs^6~o(IRKV#eUhpv74V<-7dL<7XTvc+xm#;6_a10md7})`@4oojgt=?GeDhK2k z12Dn}5J^B$2~<+>mc-4Nde6AKkG25Zv`a6&)KRDFUwi1IHFn-D5tG`4P)0-qn)(e$ zi`PMj2!JFCGmXTEzo6pFgJ;a0JhueLIF_{hfs~p=Pi1Cinmf<-uHQxuHsgklL6svV zjp7YxoM3<*u|N?JrgZV70{{K5_aF1t(H2nyM;&i{uInY{X^A;OQfyJeiQ_92b=;{U z=+6oFmUtJTreSa}785|G}&2OeGG4hv!tp2|{I zzS(7T$73y^2F~?cp3Us!ZjqQ1WF>+!LLE)5(Gv6+peT1uGp6r2u7e`V;h9z&P)%$kB zZDTJvk-#WM+Re{*yIgDR{}Lrdql_XSBx=_kQj}CYzRI^?<~@^dJ^`X3X2beTeYy9d z18XOh^03W0XDSkg(~{kg>ZgDO7)q!OLTb&S*^rmJj(G7{dC}pL;;`5M#V0TPLfgHf zn1v+eK$Mi=BMQz)^@2_;3_1k07_VCr*XynO-<);?9mR1D2g}R^VYR*Oa@I8d^AZ*m zI$B1`07DIo{V<=#;MP~_o^LHFzb!jE`)Ip~8xRwJT!R|$!jQmEX6fej;k-JAp-bDg zNvelJtGX_bN&qE95TM)>G?z^8_4ZZA+Q`F6aTeEQQb6Yu=KA4L1`LlO)Q#TtM4;9?*zD8nJ3eeU?8@TWhs^=cRYb zqdvQq+*%w?inB9UzTEGvpLk(kTcZ{wz(LwhwAjGML0Az%FwrY9md(0v%4LT$j(_K- zgfh0~{t`zSm6R&X(TLaU;gPTv*4%Nett9LD3aFBd0b!UbM@unIu!Xf5y%fIjm%4}3 zux1iT730o6V1l87!65KrbN%c|lZs+ef-1CZ*)nnOgs1v03+mBUGiKIHWWmi-jkm%$8X0|Ul{gfr@TA<@7DOxj#A zP0NGlXFvGvu$Xkk^uNd3Pd@Q|$&QiK&46LOkbzT8W1xUyNr6}xohoTtO+1-N;z>&!18Yjtt!26#<&^f&ZM7thCo)995^zo=`wHr#+~Az zscV`96(%hMCe(FPuqD=Agm4Ly`|^x#lOLS;{t?vPK`kkV#3y?G#YvmiTlvko#sm;W zbt0i{svq3k1}UU)D;JOa6uSGv5wmB+3|&lOu7B;JB`d>wE;D?ZMTumb63~ePEWDtk za`c&$P+TyQf&NFsYmT%;rI`Uy8rHj`H4S>kSQ>$jhJW0O;-h)!N% zF14WzU%y2jY{hjZgdL(y{F)QUq4!jZ*e>qULf;1;UNG{*amOVkCMCUw^h(~E=H0xZ za&MBRX;Ml-3?iUi{Wvh3dIl)BaM8-uWtS{?`Gao{5kV(ny>Y_8B`eBzU14}Mn-axP zO0Y!4u*|_2l7cZ7mW!MpV%1J}KBc2VNDC5yBt}WWd+|;uWLHuN;vWJuthHU+Mw6eK@4q2K7?M@vTzAqA7IEP2i2 zH@2*5)^x*96+1PZ>nIq+1RL7a_klqrVDRa;+@yR*=~*8S`EXrqQnjSKHRy}wx2-%33ZCxgrN#WXM^{k6gevBoDERG$4ijJFWlh& zRd4&&A*94UuNpgG;aYj%dN9DYEt}Q%aA<)JLP%V!%_P&3f{9}%^?q&O2}nwml&k;S zB|Xx#@%kSswo-%XQVK~-Vndq-QBqKve9}V?{J8I|FJD~rM^YvXTD-h`=hehToaho4 zgsC-S%CRjdDJUgQYVSw{eoyeM$RQ7<11SJ>L(#$waDw)U0NQM{)e8=?3{qJu4jV0GObPOF3E1SEyoRDzaVmOOgWy<;9dVN$w1*{fkeC!5zp+vkm4aw-1wS1_i{GJ9c2ozevdS^W_%|bXs-{T1%1sj<|1_ua z*Up-u*|u%723`%CC8LKx)n_)5REyu^9X@r?gy)V+O0A(CIMA$Yt!@6c@R!EA>B2;q z8X%x;eIK|r8$%|kMKl&#Rd&O|SLc0lI4M`XcK^av)%n*jKeH9V{1-1MU|5OAzyM2g zL{ZHl2Gxl=q@a`#C7qas5rRfmpeb<(MD?K#nU0}Nh`4ydFW&YOM~ugC-Fa?o{+-@@ z=(`Q!-1E7Mi%2+38~b!c&UOBSQYa8^^he9qa2RMCffR=#E zs7eqbLF4FM(9q)F)Ug?kI^kil0NyHs%lu5kgvqAe0!P|vG@B~X}zG;KEv|59=F{H*uCIAlYAhr>y6nCtp( z*0`L4jSt!;u?QZuNC7R9OsManplo5|$%LzjO`q9kT8~(K65FZME@)RjA*1!9B7vtU zL#e9DFzzIPu!*Kop}X8^r7pW;HH4)Gl2GShIBHZW7Hv4AUYqCos>otfHwjkxYAQ|C zG);^HS$!+vvzPlli*bgti80TUIj~Qg9~)Kuc#u`$v5KN#k=Dr`NYn{P3y%S8>b2dK zmETmHzTnL{dk;~)-{FX)bQ|@^-K&g)@0HMMn=!)h*rK6dgXUQOj9AVAx2M{@HL|U& zb#_*ES2A2xlgt}Gc{G11;W9i^TfvJe-Hu}*A zzOZ(TqaJ1v3fyE0+N5~Eh3l3eZ7Ep-B&=_;Il2AO@*c$RNQp6i~-WDk| zG|r?VlEiBSTGw?06=6KuP$n@+@T*|QMX!JKeb=sC=^@#yQ)EugNmkjAUGo({dU`rL zdmZ z!vq~L%O{jN)i-UY>M{M%DbtTpHvNP8Ae_nfe(!}z9~b=CJ7g#e!wVU)AT3UZCILL; z5g0rWf5aawShD+!&nJDJpOuwGj5ei652MzAWncFAatQ~mPf|S9q{g;qq(;jVx(L% z^xkf})5z@H$U&P^hUczMeHI@I6okeYPpRNv{4{vZ)yJARA|@sGygH!43Q@N404?`2 zXGlTnx*0T2!HSsW)Kbvf*A>ZMzJ14Q$E>9wjzo&1MqRVBX;pB|nq@_s&(uxbCKx#h z3tHCkK&oE@6%k5Fsl4E`3zwIizhrFoPsh^J*tNS~{cpDwdfvJ`VC?GYUm?@B)N^B!;edmce%Zs<)9x;TCu_3B@t2$nYH)G}pgsI#dehb|@_sN;> z9B%`UNJ{KSH@rCTi8XBB$O=7T;SOS`;bRI~r}#mG2=+mKblIe&h9+w)5bG4UD&wglHk4ylT9bL%p9*Pse&zm0U zU8ka{{!yM)aUL^VOezVeMTAl%sfv(lt28;k@~LGn&cSo&BL-KoO?&j6F;{$Av}VzM zt(u^UOA5qACA3QRfQQ;nzM}-SHN`Z4p?l2q2PX|WmVu4oh@>E5;@_5(&gC!>6^5YoP1glV219TqTv~GTEGqU{Tc`< z@PG}A4d9nPtKRlN+gFE<{OwrM7sF9Wagbw68&lb`^4>`3QFf(P(T{=X#Fdj4*PiCZ#J>NC$4_6MSoy^kE3bi| zSdb=EL@Wrn7&J?E10{I=>J(=LvlMRv4Q9W7`^$G8L-KxyW08Vz2DxqY;2Gb@UEL$P zvaux=D`e8PAV~vgmEr{oVW<58fY_Skni+I`xix2d&+p#*YF~{iB9Vu-LEv+b_G}yo zfs;Sb77D&)9OmOj4ZKPC6tcctJqpfEx_r^dn;0pztI%&hp5>KKI#g zK00>8VL|3makPm!Koy!hcdmS3)QB_I`tsIpvGO@%8bQN_WkV5;K#IwsWvUMtjW|^^ zP}HW1Yi7jEE&Vw-c;O4P=hhAw6@AQTodmA-TSW9Q9p_Uk+5-m;G*@rm&R2Z=u~Umh zTsrjb*K<=zzY;E(LVZz^d8mN21QT4uc04@8iVRUgSc*5-8^8P3?JpiPf;}9^B&7z9 zv^+3x{KVP6Ea{{Ch7AHUd#r&0C`&-9-+&f%-JmO{hKt_|kc?|W1N(qjst5J4a$V^c z+1c5V+EM@aY$T^v&pQoxf#?r>He7uU&{Laf<8C2 zDoKl*Uh0)+>m+P9OmHz5)~TJcC$^H}m`WMdT7+&VZRU-bw&Pef?rZemKWdv+i4O(55JZjp1UVGwXqy%wXQflBxOTVeZdN16+ zWm2_QM1wi!=NO+8#|ya$HuJclj!(zKBF6wn&xfP+BPtmfO2;njeV&MCm)H|tHub5u zhu{3zux1AXtv@@c5>Hdy1x!|hOIGgSqI!#FXu2@`Eh5B|7Lq6hHn^+zZw+r-er23d zaL{b)TV*HOr`t~V5Vngbi8*EpN#0$uqPX38<6Bf@sVt|fw01Se!3mL4WA$-I)w(pf z@|(h+&!ZmQ#&%GrJrs)xI1(1ArGPqK4I0Lq;KjB_fqE{GJ1jX#LKs?V>JcWSr$4|n{e(Qup*SH@5HS(#H+gvb z<-uLw=dwyl93PEAnNv0cr(%Q<5_BaX$)!VGuLd4$FL%zg6snbtfooh*-0Z3Mz25z- z2cCGfs_sPt6_MaSu=A52#yxz)l|zPiJQzxN#pMf}UC$kQ?}I1$-iyOt+^bh|LWi5? z?-Gd@fK(){^1eZ|=^FMiH)-5NiHZhzqDTvrZdQ2bo z?B{!b9Uk&13r{Gp0}0EDD4|FTt{oFdWlO-`d%s761g`;cE(Rv!(j_!zZ!W&LWE0<# zpVD!6B)+~YE_7hek0bjwJ@@{n2g%gaUP;o#-0HIZA4PUJO8|^LQC+=ph^vHYr^zj+g7!-J_*r~oe=UCTkA^CTKMYR{U>8w55-B5f^IXj zYgd|ZMFQOtBFoo@3(lo@Z0P7RUUpQ?xfP`%n$pCSS%OZea%-H%myxn_eg34MSL(eA z63?87(tSD)-x=r9cWq^^l_Gc3o$DsQJlpx%@8NV+4qf=o3-dQu4PlxAX^~xn8#S$( zv@Q63zOCDy#-&Yef9K&BFbp_pwTa>6NI67W2Fw|H*0=i$SM6rSgi(#)$sNAIg>X*! zoJ0>Rt5I_t_1{ENsaSlG*t~7Ty!3PLA2PGRoqmTADXmTo?@TK;ljkKRr$oLQ*6Z>b z0+3G})EO9`jd9amk3V(c-ey;Q8x#hKFWx-o>)}1`xaEzWWx(0ud3~pJJzf@kJe)Ks zHEzlQ-%-8&~ zL{hn{X=vlQJB$_ui6*?zkrfYLxNsp`yLPP={c$avc&6{L5iPdXzxd}OzA&2 z=AJ7D%*=Xk=c>(vJ{zB1^vrPzUdO{Jk%HbR7BY7p|KKOzm+id{6GrD8%Tb-^aRfSK z)Tt;z&MyV$ls76hzFjk9dZ)JcXMI)XYLej*!Tsl(`&t#)M(>?#fB1IO*NcBRN+HN0 zWymYzTP-(Qtt+x!uEeT6%a#wl^YV*sy2!iY;}x}$@1Mabk%EW?b@z;XC~3Q2t^K(& zw;^-u7%RjA%9xBqjc~MVf^7&ap-^rAb=chIZF)X7Bj{;pZ8Uj)KtC5?zvwM>Cx?wwZtBK$~a|GYTy{N2d56d`0HTam%C*N z!?&R%!IO#_iE(;M@K)u#%Y`)=R|ydv)TIU$000HjNklAZlyaA@3(`& zgo8g_^zF>4C@pAYQFUsCtwwdMo}*sBa=mKsiDf41iIKwG;HLR))=iyyLha=ziob%C z+S1^4;|7dbU77cY9mgz?3g5A;={`bIYHp6c%EAH_`ug97&TiMS$D4$ANdXgKpwh0uIZ~#1gzf>QF(6hY=uE2?F2Bhc2VQkxtvk49a-b;h z{ZD62z`pz1GQ_FkFC(P}&V)K--q47hmzwjNENVgxW((}Ba4tvR9>bd`aVn_PT`r5y zqe)$|hra#w*hh#2Vef4;f0}+0@m{U&!o3r z64P~V7zQM<%AZ5u&FZ{j&K!KL=9HsZ#9vKHZNz)~YxlPP)t2Axl)-r79KA|PoEH=D zoeJj_Nn}Q9K$zcR;lw?5)~eCjFX9-N-1}hrqAGO}qk2=*ZOHPnvTcb_xo-KKsjFgk za=XZxczWr2H>2D#W29B%l_4;Gi ze^;9SVZOUUqZ*eABRQwg9!-tVez>VpCd0&uaEeFlwgTOvMMTkj9*+2%@RX&i7p@$9 zUTUi{H0H6s*=AQ|b}-vr@ma{5T!(Varp&;GAD#>)J7n%RotBLow+|)$B&F6> z-!*p7jXzq2ALP429RKSEd;!AAqR~}L2_8JKczMrp`8;YP;a{B$8)-NxoU~=qx#J9wp;|a^KxSkl|i_G5&Zdo*bQTQmikTdor zJs)VZ-``{r38d9m+zgo-ccq4R&-r3_pF56n_H<1A6QtBiOxx}^Uv9VeWgpNg5*%Xi zrE382YSGvFqy5xin_Smi`_#t=SC)3&wrCNC{71D7wPRm%S4*u;#}7i9ueHxm2a|)l zum5=TldA~nN>8A7=6Co&(XWMOjpB2#RWTJ6=Ly36|e661-p@c9ZL^F%% z4FA^!y=r#&sD#82Q<3zHOmCw$%`Pn8wSUXW<+eCWs^;UrdT`o9IJe_90v(FO)hl+y8vFZX@Q;#$i0x{J9yc_y z+a%53&4cZIai%mwf_iqzC)T|*cN9v;5l<11gIJvh%L^w;=8^D^lTrgG;!MxHI?>ZU zb#A#<)v3L^Vb56uM$g1+C;IyR--UmR6o4$CkOlJEYp*p#J%5WYro4|W<$o$X|Euut zk@COi$0Xwa1tB<*{{vSFK3=gN=$-%o01#wlLPtb6LNzl)G%-drH!?voF*J3}JZk^| N002ovPDHLkV1kmN2QUBt literal 0 HcmV?d00001 diff --git a/apps/frontend-mobile/components/MedicineCard.tsx b/apps/frontend-mobile/components/MedicineCard.tsx index 5a5f91d..6799c7c 100644 --- a/apps/frontend-mobile/components/MedicineCard.tsx +++ b/apps/frontend-mobile/components/MedicineCard.tsx @@ -52,14 +52,14 @@ export function MedicineCard({ medicine }: MedicineCardProps) { const styles = StyleSheet.create({ card: { backgroundColor: colors.card, - borderRadius: borderRadius.md, + borderRadius: borderRadius.lg, padding: spacing.md, - marginHorizontal: spacing.md, + marginHorizontal: spacing.lg, marginVertical: spacing.xs, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 4, + shadowOpacity: 0.06, + shadowRadius: 8, elevation: 2, }, header: { @@ -92,7 +92,7 @@ const styles = StyleSheet.create({ price: { fontSize: 14, fontWeight: '600', - color: colors.text, + color: colors.primary, marginLeft: spacing.xs, }, labContainer: { diff --git a/apps/frontend-mobile/components/SearchBar.tsx b/apps/frontend-mobile/components/SearchBar.tsx index d2abe4e..73553d1 100644 --- a/apps/frontend-mobile/components/SearchBar.tsx +++ b/apps/frontend-mobile/components/SearchBar.tsx @@ -60,11 +60,16 @@ const styles = StyleSheet.create({ flexDirection: 'row', alignItems: 'center', backgroundColor: colors.card, - borderRadius: borderRadius.md, + borderRadius: borderRadius.lg, paddingHorizontal: spacing.md, - paddingVertical: spacing.sm, - marginHorizontal: spacing.md, + paddingVertical: spacing.sm + 2, + marginHorizontal: spacing.lg, marginVertical: spacing.sm, + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.06, + shadowRadius: 8, + elevation: 2, }, icon: { marginRight: spacing.sm, diff --git a/apps/frontend-mobile/components/StockBadge.tsx b/apps/frontend-mobile/components/StockBadge.tsx index 491eb35..52975b3 100644 --- a/apps/frontend-mobile/components/StockBadge.tsx +++ b/apps/frontend-mobile/components/StockBadge.tsx @@ -33,13 +33,13 @@ const styles = StyleSheet.create({ borderRadius: borderRadius.sm, }, success: { - backgroundColor: '#D4EDDA', + backgroundColor: '#eaf7ec', }, warning: { - backgroundColor: '#FFF3CD', + backgroundColor: '#fff3cd', }, danger: { - backgroundColor: '#F8D7DA', + backgroundColor: '#feecec', }, text: { fontSize: 12, diff --git a/apps/frontend-mobile/constants/theme.ts b/apps/frontend-mobile/constants/theme.ts index 1abd72b..60ac6fb 100644 --- a/apps/frontend-mobile/constants/theme.ts +++ b/apps/frontend-mobile/constants/theme.ts @@ -1,25 +1,47 @@ export const colors = { - // Primary - primary: '#007AFF', - primaryLight: '#4DA3FF', - primaryDark: '#0056CC', + // Primary - pastel green pharmacy palette (matching PWA) + primary: '#7fbf8f', + primaryLight: '#cfead0', + primaryDark: '#09310a', + onPrimary: '#09310a', + primaryContainer: '#cfead0', + onPrimaryContainer: '#0d2b12', + + // Secondary - soft blue + secondary: '#a3b8ff', + secondaryContainer: '#dbe7ff', + + // Tertiary - soft lavender + tertiary: '#c9b3ff', + tertiaryContainer: '#efe7ff', + + // Scan button - vivid blue (intentionally prominent) + scanButton: '#2b5bb5', + scanButtonShadow: 'rgba(43, 91, 181, 0.32)', // Semantic success: '#34C759', - danger: '#FF3B30', + danger: '#b91c1c', + dangerContainer: '#feecec', warning: '#FF9500', - // Neutral - background: '#F2F2F7', - card: '#FFFFFF', - border: '#E5E5EA', - separator: '#C6C6C8', + // Surface + background: '#fbfbfb', + card: '#ffffff', + surfaceLow: '#f2f4f5', + surface: '#eceeef', + surfaceHigh: '#e6e8e9', + border: '#c0c9bb', + separator: '#c0c9bb', // Text - text: '#1C1C1E', - textSecondary: '#8E8E93', - textInverse: '#FFFFFF', - textLink: '#007AFF', + text: '#111417', + textSecondary: '#41493e', + textInverse: '#ffffff', + textLink: '#7fbf8f', + + // Accent + accentWarm: '#f5a97a', }; export const spacing = { @@ -32,27 +54,67 @@ export const spacing = { }; export const borderRadius = { - sm: 8, - md: 12, - lg: 16, - xl: 24, + sm: 4, + md: 8, + lg: 12, + xl: 16, + full: 9999, }; export const typography = { title: { fontSize: 28, fontWeight: 'bold' as const, + color: '#111417', }, heading: { fontSize: 20, fontWeight: '600' as const, + color: '#111417', }, body: { fontSize: 16, fontWeight: 'normal' as const, + color: '#111417', + }, + bodyLarge: { + fontSize: 18, + fontWeight: '400' as const, + color: '#41493e', + lineHeight: 27, }, caption: { fontSize: 12, fontWeight: 'normal' as const, + color: '#41493e', + }, + label: { + fontSize: 14, + fontWeight: '500' as const, + color: '#41493e', + }, +}; + +export const shadows = { + soft: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.08, + shadowRadius: 20, + elevation: 3, + }, + card: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.06, + shadowRadius: 8, + elevation: 2, + }, + scanButton: { + shadowColor: '#2b5bb5', + shadowOffset: { width: 0, height: 8 }, + shadowOpacity: 0.32, + shadowRadius: 26, + elevation: 8, }, }; -- 2.52.0