From 04b83fc4199f1f0e5d9d4ea35fa5c4786e024258 Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Tue, 21 Apr 2026 21:50:31 +0200 Subject: [PATCH] refresh design system: colors, typography, native components Update color palette, dark theme tokens, typography scale, and border radius constants. Refactor native UI primitives (NativeButton, NativeList, NativeSection, NativeLabeledRow) for the new design language. Simplify OnboardingStep. Remove legacy WorkoutCard and CollectionCard components. --- src/shared/components/CollectionCard.tsx | 186 -------------- src/shared/components/OnboardingStep.tsx | 114 +++++---- src/shared/components/WorkoutCard.tsx | 232 ----------------- src/shared/components/native/NativeButton.tsx | 116 +++++++-- .../components/native/NativeLabeledRow.tsx | 65 ++++- src/shared/components/native/NativeList.tsx | 37 ++- .../components/native/NativeSection.tsx | 25 +- src/shared/components/native/index.ts | 2 +- src/shared/constants/borderRadius.ts | 16 +- src/shared/constants/colors.ts | 61 ++--- src/shared/constants/typography.ts | 235 ++++++++++-------- src/shared/theme/colors.dark.ts | 48 ++-- src/shared/theme/types.ts | 3 +- 13 files changed, 467 insertions(+), 673 deletions(-) delete mode 100644 src/shared/components/CollectionCard.tsx delete mode 100644 src/shared/components/WorkoutCard.tsx diff --git a/src/shared/components/CollectionCard.tsx b/src/shared/components/CollectionCard.tsx deleted file mode 100644 index 48ef0a7..0000000 --- a/src/shared/components/CollectionCard.tsx +++ /dev/null @@ -1,186 +0,0 @@ -/** - * CollectionCard - Premium collection card - * Used in Explore and Browse screens - * Supports 'default' and 'hero' variants - */ - -import { useMemo, useRef, useCallback } from 'react' -import { - View, - StyleSheet, - Pressable, - Animated, - ImageBackground, - useWindowDimensions, - Text as RNText, -} from 'react-native' - -import { useThemeColors, GRADIENTS } from '@/src/shared/theme' -import type { ThemeColors } from '@/src/shared/theme/types' -import { TEXT, NAVY, GREEN } from '@/src/shared/constants/colors' -import { RADIUS } from '@/src/shared/constants/borderRadius' -import { SPACING, LAYOUT } from '@/src/shared/constants/spacing' -import { StyledText } from './StyledText' -import type { Collection } from '@/src/shared/types' - -const AnimatedPressable = Animated.createAnimatedComponent(Pressable) - -export type CollectionCardVariant = 'default' | 'hero' | 'horizontal' - -interface CollectionCardProps { - collection: Collection - variant?: CollectionCardVariant - onPress?: () => void - imageUrl?: string - workoutCountLabel?: string -} - -export function CollectionCard({ collection, variant = 'default', onPress, imageUrl, workoutCountLabel }: CollectionCardProps) { - const colors = useThemeColors() - const { width: screenWidth } = useWindowDimensions() - const styles = useMemo(() => createStyles(colors, screenWidth, variant), [colors, screenWidth, variant]) - - // Press animation - const scaleValue = useRef(new Animated.Value(1)).current - const handlePressIn = useCallback(() => { - Animated.spring(scaleValue, { - toValue: 0.97, - useNativeDriver: true, - speed: 50, - bounciness: 4, - }).start() - }, [scaleValue]) - const handlePressOut = useCallback(() => { - Animated.spring(scaleValue, { - toValue: 1, - useNativeDriver: true, - speed: 30, - bounciness: 6, - }).start() - }, [scaleValue]) - - const countLabel = workoutCountLabel ?? `${collection.workoutIds.length} workouts` - - return ( - - {/* Background Image or Solid Color */} - {imageUrl ? ( - - - - ) : ( - - )} - - {/* Content */} - - - {collection.icon} - - - - {collection.title} - - - {variant === 'hero' && ( - - {collection.description} - - )} - - - {countLabel} - - - - ) -} - -function createStyles(colors: ThemeColors, screenWidth: number, variant: CollectionCardVariant) { - const defaultCardWidth = (screenWidth - LAYOUT.SCREEN_PADDING * 2 - SPACING[3]) / 2 - const horizontalCardWidth = screenWidth * 0.65 - - const containerByVariant = { - default: { - width: defaultCardWidth, - aspectRatio: 1 as number, - }, - hero: { - width: screenWidth - LAYOUT.SCREEN_PADDING * 2, - height: 200, - }, - horizontal: { - width: horizontalCardWidth, - height: 180, - }, - } - - return StyleSheet.create({ - container: { - ...containerByVariant[variant], - borderRadius: RADIUS.XL, - overflow: 'hidden', - }, - content: { - flex: 1, - padding: SPACING[4], - justifyContent: 'flex-end', - }, - iconContainer: { - width: 48, - height: 48, - borderRadius: RADIUS.LG, - backgroundColor: GREEN.DIM, - justifyContent: 'center', - alignItems: 'center', - marginBottom: SPACING[3], - borderWidth: 1, - borderColor: GREEN.BORDER, - }, - icon: { - fontSize: 24, - }, - title: { - marginBottom: SPACING[1], - }, - }) -} diff --git a/src/shared/components/OnboardingStep.tsx b/src/shared/components/OnboardingStep.tsx index 420a5f6..99d4bed 100644 --- a/src/shared/components/OnboardingStep.tsx +++ b/src/shared/components/OnboardingStep.tsx @@ -1,19 +1,20 @@ /** * TabataFit OnboardingStep * Reusable wrapper for each onboarding screen — progress bar, animation, layout + * + * Revamped: fade-up entrance, segmented progress, generous spacing */ import { useRef, useEffect, useMemo } from 'react' -import { View, StyleSheet, Animated, Dimensions, Pressable } from 'react-native' +import { View, StyleSheet, Animated, Pressable } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { Icon } from './Icon' -import { useThemeColors, BRAND } from '../theme' +import { useThemeColors } from '../theme' import type { ThemeColors } from '../theme/types' import { SPACING, LAYOUT } from '../constants/spacing' import { RADIUS } from '../constants/borderRadius' -import { DURATION, EASE } from '../constants/animations' - -const { width: SCREEN_WIDTH } = Dimensions.get('window') +import { DURATION, EASE, SPRING } from '../constants/animations' +import { GREEN } from '../constants/colors' interface OnboardingStepProps { step: number @@ -26,62 +27,74 @@ export function OnboardingStep({ step, totalSteps, children, onBack }: Onboardin const colors = useThemeColors() const styles = useMemo(() => createStyles(colors), [colors]) const insets = useSafeAreaInsets() - const slideAnim = useRef(new Animated.Value(SCREEN_WIDTH)).current const fadeAnim = useRef(new Animated.Value(0)).current - const progressAnim = useRef(new Animated.Value(0)).current + const slideAnim = useRef(new Animated.Value(24)).current + const progressAnims = useRef( + Array.from({ length: totalSteps }, () => new Animated.Value(0)) + ).current useEffect(() => { - // Reset position for new step - slideAnim.setValue(SCREEN_WIDTH) + // Reset for new step — fade up instead of slide right fadeAnim.setValue(0) + slideAnim.setValue(24) - // Animate in Animated.parallel([ - Animated.timing(slideAnim, { - toValue: 0, - duration: DURATION.NORMAL, - easing: EASE.EASE_OUT, - useNativeDriver: true, - }), Animated.timing(fadeAnim, { toValue: 1, duration: DURATION.NORMAL, easing: EASE.EASE_OUT, useNativeDriver: true, }), + Animated.spring(slideAnim, { + toValue: 0, + ...SPRING.GENTLE, + useNativeDriver: true, + }), ]).start() - // Animate progress bar - Animated.timing(progressAnim, { - toValue: step / totalSteps, - duration: DURATION.SLOW, - easing: EASE.EASE_OUT, - useNativeDriver: false, - }).start() + // Animate progress segments + progressAnims.forEach((anim, i) => { + Animated.spring(anim, { + toValue: i < step ? 1 : 0, + ...SPRING.SNAPPY, + useNativeDriver: false, + }).start() + }) }, [step]) - const progressWidth = progressAnim.interpolate({ - inputRange: [0, 1], - outputRange: ['0%', '100%'], - }) - return ( - - {/* Progress bar */} - - + + {/* Segmented progress bar */} + + {progressAnims.map((anim, i) => ( + + + + ))} {/* Back button — visible on steps 2+ */} - {onBack && step > 1 && ( + {onBack && step > 1 ? ( - + + ) : ( + )} {/* Step content */} @@ -89,10 +102,10 @@ export function OnboardingStep({ step, totalSteps, children, onBack }: Onboardin style={[ styles.content, { - transform: [{ translateX: slideAnim }], + transform: [{ translateY: slideAnim }], opacity: fadeAnim, }, - { paddingBottom: insets.bottom + SPACING[6] }, + { paddingBottom: insets.bottom + SPACING[8] }, ]} > {children} @@ -107,30 +120,37 @@ function createStyles(colors: ThemeColors) { flex: 1, backgroundColor: colors.bg.base, }, - progressTrack: { - height: 3, - backgroundColor: colors.bg.surface, + progressRow: { + flexDirection: 'row', + gap: SPACING[1], marginHorizontal: LAYOUT.SCREEN_PADDING, - borderRadius: RADIUS.XS, + }, + segmentTrack: { + flex: 1, + height: 4, + backgroundColor: colors.bg.overlay2, + borderRadius: RADIUS.PILL, overflow: 'hidden', }, - progressFill: { + segmentFill: { height: '100%', - backgroundColor: BRAND.PRIMARY, - borderRadius: RADIUS.XS, + backgroundColor: GREEN[500], + borderRadius: RADIUS.PILL, }, backButton: { marginTop: SPACING[3], - marginLeft: SPACING[3], - width: 40, - height: 40, + marginLeft: SPACING[2], + width: LAYOUT.TOUCH_TARGET, + height: LAYOUT.TOUCH_TARGET, alignItems: 'center', justifyContent: 'center', }, + backSpacer: { + height: SPACING[3] + LAYOUT.TOUCH_TARGET, + }, content: { flex: 1, paddingHorizontal: LAYOUT.SCREEN_PADDING, - paddingTop: SPACING[8], }, }) } diff --git a/src/shared/components/WorkoutCard.tsx b/src/shared/components/WorkoutCard.tsx deleted file mode 100644 index 6f76aa6..0000000 --- a/src/shared/components/WorkoutCard.tsx +++ /dev/null @@ -1,232 +0,0 @@ -/** - * WorkoutCard - Dark Medical workout card - * Flat navy surface with border-dim, no glassmorphism - */ - -import { useMemo, useRef, useCallback } from 'react' -import { - View, - StyleSheet, - Pressable, - Animated, - ImageBackground, - useWindowDimensions, - Text as RNText, - ViewStyle, -} from 'react-native' -import { LinearGradient } from 'expo-linear-gradient' -import { Icon } from './Icon' - -import { useThemeColors } from '@/src/shared/theme' -import { BRAND, GRADIENTS, TEXT, NAVY, BORDER_COLORS, PHASE } from '@/src/shared/constants/colors' -import { FONT_FAMILY_SANS_SEMIBOLD } from '@/src/shared/constants/typography' -import type { ThemeColors } from '@/src/shared/theme/types' -import { RADIUS } from '@/src/shared/constants/borderRadius' -import { SPACING } from '@/src/shared/constants/spacing' -import { StyledText } from './StyledText' -import type { Workout, WorkoutCategory } from '@/src/shared/types' - -const AnimatedPressable = Animated.createAnimatedComponent(Pressable) - -export type WorkoutCardVariant = 'horizontal' | 'grid' | 'featured' - -interface WorkoutCardProps { - workout: Workout - variant?: WorkoutCardVariant - onPress?: () => void - title?: string - metadata?: string - trainerName?: string - isLocked?: boolean -} - -export const CATEGORY_COLORS: Record = { - 'full-body': BRAND.PRIMARY, - 'core': BRAND.INFO, - 'upper-body': TEXT.SECONDARY, - 'lower-body': BRAND.PRIMARY, - 'cardio': PHASE.PREP, -} - -const CATEGORY_LABELS: Record = { - 'full-body': 'Full Body', - 'core': 'Core', - 'upper-body': 'Upper Body', - 'lower-body': 'Lower Body', - 'cardio': 'Cardio', -} - -function getVariantDimensions(variant: WorkoutCardVariant, screenWidth: number): ViewStyle { - switch (variant) { - case 'featured': - return { - width: screenWidth - SPACING[6] * 2, - height: 320, - } - case 'horizontal': - return { - width: 200, - height: 280, - } - case 'grid': - default: - return { - flex: 1, - aspectRatio: 0.75, - } - } -} - -export function WorkoutCard({ - workout, - variant = 'horizontal', - onPress, - title, - metadata, - trainerName, - isLocked, -}: WorkoutCardProps) { - const colors = useThemeColors() - const { width: screenWidth } = useWindowDimensions() - const styles = useMemo(() => createStyles(colors), [colors]) - const dimensions = useMemo(() => getVariantDimensions(variant, screenWidth), [variant, screenWidth]) - - // Press animation - const scaleValue = useRef(new Animated.Value(1)).current - const handlePressIn = useCallback(() => { - Animated.spring(scaleValue, { - toValue: 0.97, - useNativeDriver: true, - speed: 50, - bounciness: 4, - }).start() - }, [scaleValue]) - const handlePressOut = useCallback(() => { - Animated.spring(scaleValue, { - toValue: 1, - useNativeDriver: true, - speed: 30, - bounciness: 6, - }).start() - }, [scaleValue]) - - const displayTitle = title ?? workout.title - const metaParts = [ - `${workout.duration} min`, - `${workout.calories} cal`, - ...(trainerName ? [trainerName] : []), - ] - const displayMetadata = metadata ?? metaParts.join(' · ') - const categoryColor = CATEGORY_COLORS[workout.category] - - return ( - - {/* Background Image */} - - {/* Dark overlay for text readability */} - - - - {/* Category Badge — flat navy */} - - - {CATEGORY_LABELS[workout.category]} - - - - {/* Play Button — flat navy circle */} - - - - - - - {/* Content at Bottom */} - - - {displayTitle} - - - {displayMetadata} - - - - ) -} - -function createStyles(_colors: ThemeColors) { - return StyleSheet.create({ - container: { - borderRadius: RADIUS.LG, - borderCurve: 'continuous', - overflow: 'hidden', - backgroundColor: NAVY[800], - borderWidth: 1, - borderColor: BORDER_COLORS.DIM, - }, - categoryBadge: { - position: 'absolute', - top: SPACING[3], - left: SPACING[3], - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: SPACING[3], - paddingVertical: SPACING[1], - borderRadius: RADIUS.SM, - borderCurve: 'continuous', - borderWidth: 1, - overflow: 'hidden', - }, - categoryText: { - fontSize: 11, - fontFamily: FONT_FAMILY_SANS_SEMIBOLD, - }, - playButtonContainer: { - ...StyleSheet.absoluteFillObject, - justifyContent: 'center', - alignItems: 'center', - }, - playButton: { - width: 56, - height: 56, - borderRadius: RADIUS.FULL, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: 'rgba(17,34,64,0.7)', // Semi-transparent NAVY[800] overlay - borderWidth: 1, - borderColor: BORDER_COLORS.DIM, - }, - content: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - padding: SPACING[4], - }, - title: { - marginBottom: SPACING[1], - }, - }) -} diff --git a/src/shared/components/native/NativeButton.tsx b/src/shared/components/native/NativeButton.tsx index a4192fc..316059d 100644 --- a/src/shared/components/native/NativeButton.tsx +++ b/src/shared/components/native/NativeButton.tsx @@ -1,10 +1,22 @@ -import { Pressable, StyleSheet, Text, type ViewStyle } from 'react-native' +import { Pressable, StyleSheet, Text, View, type ViewStyle, type TextStyle } from 'react-native' +import { Image } from 'expo-image' import type { SFSymbol } from 'sf-symbols-typescript' -import { GREEN, NAVY, RED, TEXT } from '@/src/shared/constants/colors' +import { GREEN, NAVY, RED, TEXT, BRAND } from '@/src/shared/constants/colors' import { RADIUS } from '@/src/shared/constants/borderRadius' -import { LAYOUT } from '@/src/shared/constants/spacing' +import { SPACING, LAYOUT } from '@/src/shared/constants/spacing' +/** + * SwiftUI-inspired button variants: + * - borderedProminent: filled green (primary CTA) + * - bordered: tinted background with colored text + * - borderless: text-only (inline actions) + * - destructive: red text action + * - plain: minimal, no chrome + */ type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'destructive' | 'icon' + | 'borderedProminent' | 'bordered' | 'borderless' | 'plain' + +type ControlSize = 'mini' | 'small' | 'regular' | 'large' | 'extraLarge' export interface NativeButtonProps { variant?: ButtonVariant @@ -13,17 +25,29 @@ export interface NativeButtonProps { onPress?: () => void disabled?: boolean color?: string - controlSize?: 'mini' | 'small' | 'regular' | 'large' + controlSize?: ControlSize fullWidth?: boolean style?: ViewStyle testID?: string } export function NativeButton(props: NativeButtonProps) { - const { variant = 'primary', title, onPress, disabled, fullWidth, style, testID } = props + const { + variant = 'primary', + title, + systemImage, + onPress, + disabled, + color, + controlSize = 'regular', + fullWidth, + style, + testID, + } = props - const buttonStyle = getVariantStyle(variant) - const textStyle = getVariantTextStyle(variant) + const sizeStyle = SIZE_STYLES[controlSize] + const buttonStyle = getVariantStyle(variant, color) + const textStyle = getVariantTextStyle(variant, color) return ( [ styles.base, + sizeStyle.container, fullWidth && styles.fullWidth, buttonStyle, pressed && styles.pressed, disabled && styles.disabled, + { borderCurve: 'continuous' } as ViewStyle, style, ]} testID={testID} > - {title ? {title} : null} + + {systemImage ? ( + + ) : null} + {title ? ( + {title} + ) : null} + ) } -function getVariantStyle(variant: ButtonVariant): ViewStyle { +function getVariantStyle(variant: ButtonVariant, color?: string): ViewStyle { + const brandColor = color ?? GREEN[500] switch (variant) { case 'primary': - return { backgroundColor: GREEN[500] } + case 'borderedProminent': + return { backgroundColor: brandColor } case 'secondary': - return { backgroundColor: 'transparent', borderWidth: 1.5, borderColor: GREEN[500] } + case 'bordered': + return { backgroundColor: `${brandColor}18` } // 10% opacity tint case 'ghost': + case 'borderless': + case 'plain': + return { backgroundColor: 'transparent' } case 'destructive': + return { backgroundColor: 'transparent' } case 'icon': return { backgroundColor: 'transparent' } } } -function getVariantTextStyle(variant: ButtonVariant) { +function getVariantTextStyle(variant: ButtonVariant, color?: string): TextStyle { + const brandColor = color ?? GREEN[500] switch (variant) { case 'primary': + case 'borderedProminent': return { color: NAVY[900] } + case 'secondary': + case 'bordered': + return { color: brandColor } case 'destructive': return { color: RED[500] } + case 'ghost': + case 'borderless': + return { color: brandColor } + case 'plain': + return { color: TEXT.PRIMARY } default: return { color: TEXT.PRIMARY } } } +const SIZE_STYLES: Record = { + mini: { + container: { height: 28, paddingHorizontal: SPACING[2], borderRadius: RADIUS.SM }, + text: { fontSize: 13 }, + }, + small: { + container: { height: 34, paddingHorizontal: SPACING[3], borderRadius: RADIUS.SM }, + text: { fontSize: 14 }, + }, + regular: { + container: { height: 44, paddingHorizontal: SPACING[4], borderRadius: RADIUS.MD }, + text: { fontSize: 17 }, + }, + large: { + container: { height: 50, paddingHorizontal: SPACING[5], borderRadius: RADIUS.MD }, + text: { fontSize: 17 }, + }, + extraLarge: { + container: { height: LAYOUT.BUTTON_HEIGHT, paddingHorizontal: SPACING[6], borderRadius: RADIUS.MD }, + text: { fontSize: 17 }, + }, +} + const styles = StyleSheet.create({ base: { - height: LAYOUT.BUTTON_HEIGHT, - borderRadius: RADIUS.MD, alignItems: 'center', justifyContent: 'center', minHeight: LAYOUT.TOUCH_TARGET, }, + content: { + flexDirection: 'row', + alignItems: 'center', + gap: SPACING[2], + }, fullWidth: { width: '100%', }, + icon: { + width: 18, + height: 18, + }, text: { - fontSize: 15, fontWeight: '600', textAlign: 'center', }, pressed: { - opacity: 0.8, + opacity: 0.7, transform: [{ scale: 0.98 }], }, disabled: { - opacity: 0.4, + opacity: 0.35, }, }) diff --git a/src/shared/components/native/NativeLabeledRow.tsx b/src/shared/components/native/NativeLabeledRow.tsx index 7e9268a..7661583 100644 --- a/src/shared/components/native/NativeLabeledRow.tsx +++ b/src/shared/components/native/NativeLabeledRow.tsx @@ -1,33 +1,68 @@ import { StyleSheet, Text, View, Pressable } from 'react-native' -import { TEXT } from '@/src/shared/constants/colors' +import { Image } from 'expo-image' +import { BRAND, TEXT } from '@/src/shared/constants/colors' import { SPACING } from '@/src/shared/constants/spacing' export interface NativeLabeledRowProps { label: string value?: string + /** SF Symbol name for expo-image source="sf:name" */ icon?: string + /** Icon tint color */ + iconColor?: string chevron?: boolean onPress?: () => void + /** Destructive row — label turns red */ + destructive?: boolean children?: React.ReactNode testID?: string } export function NativeLabeledRow(props: NativeLabeledRowProps) { - const { label, value, chevron, onPress, children } = props + const { + label, + value, + icon, + iconColor, + chevron, + onPress, + destructive, + children, + } = props + + const labelColor = destructive ? BRAND.DANGER : TEXT.PRIMARY const content = ( - {label} + {icon ? ( + + ) : null} + {label} {value ? {value} : null} {children} - {chevron ? : null} + {(chevron || onPress) ? ( + + ) : null} ) if (onPress) { - return {content} + return ( + pressed ? styles.pressed : undefined} + > + {content} + + ) } return content @@ -42,9 +77,14 @@ const styles = StyleSheet.create({ paddingHorizontal: SPACING[4], paddingVertical: SPACING[3], }, + icon: { + width: 22, + height: 22, + marginRight: SPACING[3], + }, label: { fontSize: 17, - color: TEXT.PRIMARY, + fontWeight: '400', flex: 1, }, right: { @@ -54,11 +94,16 @@ const styles = StyleSheet.create({ }, value: { fontSize: 17, + fontWeight: '400', color: TEXT.TERTIARY, }, - chevron: { - fontSize: 20, - color: TEXT.TERTIARY, - marginLeft: SPACING[1], + chevronIcon: { + width: 13, + height: 13, + tintColor: TEXT.TERTIARY, + opacity: 0.6, + }, + pressed: { + opacity: 0.7, }, }) diff --git a/src/shared/components/native/NativeList.tsx b/src/shared/components/native/NativeList.tsx index 90c8b5c..da5f4ac 100644 --- a/src/shared/components/native/NativeList.tsx +++ b/src/shared/components/native/NativeList.tsx @@ -1,5 +1,5 @@ import { type ViewStyle, View, StyleSheet } from 'react-native' -import { NAVY } from '@/src/shared/constants/colors' +import { NAVY, BORDER_COLORS } from '@/src/shared/constants/colors' import { SPACING, LAYOUT } from '@/src/shared/constants/spacing' import { RADIUS } from '@/src/shared/constants/borderRadius' @@ -8,13 +8,20 @@ export interface NativeListProps { style?: ViewStyle scrollEnabled?: boolean height?: number + /** Remove default margin (for embedding inside other containers) */ + inset?: boolean testID?: string } export function NativeList(props: NativeListProps) { - const { children, style, height } = props + const { children, style, height, inset = true } = props return ( - + {children} ) @@ -24,13 +31,31 @@ export function calculateListHeight(rows: number, sections: number = 1): number return rows * LAYOUT.LIST_ROW_HEIGHT + sections * LAYOUT.LIST_HEADER_HEIGHT + 20 } +/** Thin iOS-style separator for use between list rows */ +export function ListSeparator() { + return ( + + + + ) +} + const styles = StyleSheet.create({ list: { marginHorizontal: LAYOUT.SCREEN_PADDING, - marginTop: LAYOUT.SECTION_GAP, - marginBottom: SPACING[3], backgroundColor: NAVY[800], - borderRadius: RADIUS.XL, + borderRadius: RADIUS.LG, + borderCurve: 'continuous', overflow: 'hidden', }, + noInset: { + marginHorizontal: 0, + }, + separatorOuter: { + paddingLeft: SPACING[4], + }, + separator: { + height: StyleSheet.hairlineWidth, + backgroundColor: BORDER_COLORS.SEPARATOR, + }, }) diff --git a/src/shared/components/native/NativeSection.tsx b/src/shared/components/native/NativeSection.tsx index c3fbaf7..5c8a84a 100644 --- a/src/shared/components/native/NativeSection.tsx +++ b/src/shared/components/native/NativeSection.tsx @@ -2,15 +2,15 @@ import { StyleSheet, Text, View } from 'react-native' import { NAVY, TEXT } from '@/src/shared/constants/colors' import { SPACING, LAYOUT } from '@/src/shared/constants/spacing' import { RADIUS } from '@/src/shared/constants/borderRadius' -import { FONT_FAMILY } from '@/src/shared/constants/typography' export interface NativeSectionProps { title?: string + footer?: string children: React.ReactNode } export function NativeSection(props: NativeSectionProps) { - const { title, children } = props + const { title, footer, children } = props return ( {title ? ( @@ -19,6 +19,9 @@ export function NativeSection(props: NativeSectionProps) { {children} + {footer ? ( + {footer} + ) : null} ) } @@ -29,16 +32,26 @@ const styles = StyleSheet.create({ }, header: { fontSize: 13, - fontFamily: FONT_FAMILY.SANS_SEMIBOLD, + fontWeight: '400', color: TEXT.TERTIARY, - letterSpacing: 0.06, + letterSpacing: -0.08, textTransform: 'uppercase', - paddingHorizontal: LAYOUT.SCREEN_PADDING, + paddingHorizontal: LAYOUT.SCREEN_PADDING + LAYOUT.CARD_PADDING, marginBottom: SPACING[2], }, content: { + marginHorizontal: LAYOUT.SCREEN_PADDING, backgroundColor: NAVY[800], - borderRadius: RADIUS.XL, + borderRadius: RADIUS.LG, overflow: 'hidden', }, + footer: { + fontSize: 13, + fontWeight: '400', + color: TEXT.TERTIARY, + letterSpacing: -0.08, + paddingHorizontal: LAYOUT.SCREEN_PADDING + LAYOUT.CARD_PADDING, + marginTop: SPACING[2], + lineHeight: 18, + }, }) diff --git a/src/shared/components/native/index.ts b/src/shared/components/native/index.ts index f6ed387..233d45a 100644 --- a/src/shared/components/native/index.ts +++ b/src/shared/components/native/index.ts @@ -1,7 +1,7 @@ export { NativeButton } from './NativeButton' export type { NativeButtonProps } from './NativeButton' -export { NativeList, calculateListHeight } from './NativeList' +export { NativeList, calculateListHeight, ListSeparator } from './NativeList' export type { NativeListProps } from './NativeList' export { NativeSection } from './NativeSection' diff --git a/src/shared/constants/borderRadius.ts b/src/shared/constants/borderRadius.ts index 8371042..fb0e606 100644 --- a/src/shared/constants/borderRadius.ts +++ b/src/shared/constants/borderRadius.ts @@ -1,17 +1,17 @@ /** * TabataFit Border Radius System - * Dark Medical — clean, minimal rounding + * iOS-native continuous corners (borderCurve: 'continuous') */ export const RADIUS = { NONE: 0, - XS: 2, // Progress bar, hairline element - SM: 6, // Badge, chip, tag - MD: 10, // Button, input, tip card - LG: 14, // Standard program card - XL: 18, // Large card, modal - '2XL': 22, // Icon container, medium element - '3XL': 28, // Large element + XS: 4, // Progress bar, hairline element + SM: 8, // Badge, chip, tag + MD: 12, // Button, input, tip card — iOS standard + LG: 16, // Standard card — iOS grouped inset + XL: 20, // Large card, modal, bottom sheet + '2XL': 24, // Icon container, medium element + '3XL': 32, // Large element // Special PILL: 9999, // Pill, toggle, progress bar diff --git a/src/shared/constants/colors.ts b/src/shared/constants/colors.ts index 9025f44..471254c 100644 --- a/src/shared/constants/colors.ts +++ b/src/shared/constants/colors.ts @@ -1,17 +1,18 @@ /** * TabataFit Color System - * Dark Medical — navy backgrounds, green actions, no shadows + * Dark Premium — refined navy backgrounds, green actions, native iOS feel */ // ═══════════════════════════════════════════════════════════════════════════ -// NAVY (Backgrounds) +// NAVY (Backgrounds) — warmer, closer to iOS dark mode tones // ═══════════════════════════════════════════════════════════════════════════ export const NAVY = { - 900: '#0D1B2A', // Main app background - 800: '#112240', // Surface 1 — default cards - 700: '#1A3050', // Surface 2 — elevated cards - 600: '#243C5E', // Active borders + 900: '#0A1628', // Main app background — deep, warm navy + 800: '#111D2E', // Surface 1 — default cards (grouped inset bg) + 700: '#192A3E', // Surface 2 — elevated cards + 600: '#223750', // Active borders, selection + 500: '#2C4566', // Tertiary surface / hover state } as const // ═══════════════════════════════════════════════════════════════════════════ @@ -22,8 +23,8 @@ export const GREEN = { 500: '#00C896', // Primary CTA, effort timer, progress 600: '#00A67C', // Hover/pressed state 700: '#00875F', // Deep active state - DIM: 'rgba(0,200,150,0.12)', // Badge/chip/card accent background - BORDER: 'rgba(0,200,150,0.35)', // Card accent border + DIM: 'rgba(0,200,150,0.10)', // Badge/chip/card accent background + BORDER: 'rgba(0,200,150,0.30)', // Card accent border } as const // ═══════════════════════════════════════════════════════════════════════════ @@ -31,18 +32,19 @@ export const GREEN = { // ═══════════════════════════════════════════════════════════════════════════ export const TEXT = { - PRIMARY: '#E6F1FF', // white-100 - SECONDARY: '#A8B2D8', // slate-300 - TERTIARY: '#8892B0', // slate-400 - MUTED: '#8892B0', - HINT: '#8892B0', - DISABLED: '#3A3A3C', + PRIMARY: '#ECEFF4', // High-contrast primary (Nord Snow Storm inspired) + SECONDARY: '#9BA4B5', // Secondary text + TERTIARY: '#6B7A8D', // Tertiary, placeholders — lower contrast than before + MUTED: '#6B7A8D', + HINT: '#6B7A8D', + DISABLED: '#3A4555', } as const export const BORDER_COLORS = { - DIM: 'rgba(168,178,216,0.15)', // Default border - HOVER: 'rgba(168,178,216,0.25)', // Hover border - BRAND: 'rgba(0,200,150,0.35)', // Green border (matches GREEN.BORDER) + DIM: 'rgba(150,164,190,0.12)', // Default border — softer + HOVER: 'rgba(150,164,190,0.20)', // Hover border + BRAND: 'rgba(0,200,150,0.30)', // Green border + SEPARATOR: 'rgba(150,164,190,0.08)', // iOS-style thin separator } as const // ═══════════════════════════════════════════════════════════════════════════ @@ -52,7 +54,7 @@ export const BORDER_COLORS = { export const ORANGE = { 500: '#FF8A5C', 600: '#E06A3C', - DIM: 'rgba(255,138,92,0.12)', + DIM: 'rgba(255,138,92,0.10)', } as const // ═══════════════════════════════════════════════════════════════════════════ @@ -60,8 +62,8 @@ export const ORANGE = { // ═══════════════════════════════════════════════════════════════════════════ export const RED = { - 500: '#FF4444', // Timer emergency <10s ONLY - DIM: 'rgba(255,68,68,0.12)', // Danger background tint + 500: '#FF453A', // iOS system red — timer emergency <10s + DIM: 'rgba(255,69,58,0.12)', } as const // ═══════════════════════════════════════════════════════════════════════════ @@ -73,7 +75,8 @@ export const BRAND = { SECONDARY: GREEN['600'], DANGER: RED['500'], SUCCESS: GREEN['500'], - INFO: '#85C7F2', + INFO: '#64D2FF', // iOS system cyan + LINK: '#0A84FF', // iOS system blue — for tappable text } as const // ═══════════════════════════════════════════════════════════════════════════ @@ -84,9 +87,9 @@ export const DARK = { BASE: NAVY['900'], SURFACE: NAVY['800'], ELEVATED: NAVY['700'], - OVERLAY_1: 'rgba(168,178,216,0.06)', - OVERLAY_2: 'rgba(168,178,216,0.10)', - OVERLAY_3: 'rgba(168,178,216,0.15)', + OVERLAY_1: 'rgba(150,164,190,0.05)', + OVERLAY_2: 'rgba(150,164,190,0.08)', + OVERLAY_3: 'rgba(150,164,190,0.12)', SCRIM: 'rgba(0,0,0,0.6)', } as const @@ -102,16 +105,16 @@ export const PHASE = { WORK_LIGHT: GREEN.DIM, WORK_GLOW: 'rgba(0,200,150,0.5)', - REST: '#8892B0', // slate-400 — visual rest signal - REST_LIGHT: 'rgba(136,146,176,0.2)', - REST_GLOW: 'rgba(136,146,176,0.5)', + REST: '#6B7A8D', // Muted tertiary — visual rest signal + REST_LIGHT: 'rgba(107,122,141,0.2)', + REST_GLOW: 'rgba(107,122,141,0.5)', COMPLETE: GREEN['500'], COMPLETE_LIGHT: GREEN.DIM, } as const // ═══════════════════════════════════════════════════════════════════════════ -// GRADIENTS (video overlays only — no glass shimmer) +// GRADIENTS (video overlays only) // ═══════════════════════════════════════════════════════════════════════════ export const AMBER = { @@ -121,7 +124,7 @@ export const AMBER = { export const GRADIENTS = { VIDEO_OVERLAY: ['transparent', 'rgba(0,0,0,0.8)'], VIDEO_TOP: ['rgba(0,0,0,0.5)', 'transparent'], - CARD_OVERLAY: ['transparent', 'rgba(13,27,42,0.85)'] as const, + CARD_OVERLAY: ['transparent', 'rgba(10,22,40,0.85)'] as const, ASSESSMENT_CTA: [ORANGE[500], RED[500]] as const, } as const diff --git a/src/shared/constants/typography.ts b/src/shared/constants/typography.ts index 8697644..724d249 100644 --- a/src/shared/constants/typography.ts +++ b/src/shared/constants/typography.ts @@ -1,27 +1,42 @@ /** * TabataFit Typography System - * Three families: Serif (emotional), Sans (interface), Mono (data) + * SF Pro (system font) — premium native iOS feel + * + * Uses Apple's Dynamic Type scale with system font. + * No custom fonts loaded — SF Pro is the default on iOS. + * fontWeight controls the visual weight; no fontFamily needed for SF Pro. + * + * For data/timer: system monospaced via fontVariant: ['tabular-nums'] */ -import { TextStyle } from 'react-native' +import { Platform, TextStyle } from 'react-native' // ═══════════════════════════════════════════════════════════════════════════ -// FONT FAMILIES +// FONT FAMILIES — System font (SF Pro on iOS, Roboto on Android) // ═══════════════════════════════════════════════════════════════════════════ +/** + * On iOS, omitting fontFamily uses SF Pro automatically. + * On Android, omitting fontFamily uses Roboto. + * We use 'System' as a semantic marker — React Native resolves it to the + * platform system font. + */ export const FONT_FAMILY = { - SERIF: 'DMSerifDisplay_Regular', - SERIF_ITALIC: 'DMSerifDisplay_Italic', - SANS: 'Outfit_400Regular', - SANS_MEDIUM: 'Outfit_500Medium', - SANS_SEMIBOLD: 'Outfit_600SemiBold', - SANS_BOLD: 'Outfit_700Bold', - MONO: 'DMMono_400Regular', - MONO_MEDIUM: 'DMMono_500Medium', + // System font — no fontFamily needed (undefined = system font in RN) + SANS: undefined, + SANS_MEDIUM: undefined, + SANS_SEMIBOLD: undefined, + SANS_BOLD: undefined, + // Serif — iOS has New York via serif fontFamily + SERIF: Platform.OS === 'ios' ? 'Georgia' : 'serif', + SERIF_ITALIC: Platform.OS === 'ios' ? 'Georgia' : 'serif', + // Monospace — system mono (SF Mono on iOS, Roboto Mono on Android) + MONO: Platform.OS === 'ios' ? 'Menlo' : 'monospace', + MONO_MEDIUM: Platform.OS === 'ios' ? 'Menlo' : 'monospace', } as const // Font alias object — maps to FONT_FAMILY values -const FONT: Record = { +const FONT = { SANS: FONT_FAMILY.SANS, SANS_MEDIUM: FONT_FAMILY.SANS_MEDIUM, SANS_SEMIBOLD: FONT_FAMILY.SANS_SEMIBOLD, @@ -36,212 +51,217 @@ const FONT: Record = { SEMIBOLD: FONT_FAMILY.SANS_SEMIBOLD, BOLD: FONT_FAMILY.SANS_BOLD, BLACK: FONT_FAMILY.SANS_BOLD, -} +} as const // ═══════════════════════════════════════════════════════════════════════════ -// TYPE SCALE +// TYPE SCALE — matches Apple HIG Dynamic Type defaults // ═══════════════════════════════════════════════════════════════════════════ export const TYPOGRAPHY = { - // Display / Hero — Serif for emotional moments + // Display / Hero — bold system font for emotional moments DISPLAY: { fontFamily: FONT.SERIF, fontSize: 28, - lineHeight: 34, - letterSpacing: 0, - } as TextStyle, - - HERO: { - fontFamily: FONT.SERIF, - fontSize: 32, - lineHeight: 38, - letterSpacing: -0.5, - } as TextStyle, - - // Large Title (like iOS) - LARGE_TITLE: { - fontFamily: FONT.SERIF, - fontSize: 34, - lineHeight: 41, - letterSpacing: 0.37, - } as TextStyle, - - // Heading 1 — Serif for section titles - HEADING_1: { - fontFamily: FONT.SERIF, - fontSize: 22, - lineHeight: 28, - letterSpacing: 0.35, - } as TextStyle, - - // Title 1 (backward compat) - TITLE_1: { - fontFamily: FONT.SERIF, - fontSize: 28, + fontWeight: '700' as const, lineHeight: 34, letterSpacing: 0.36, } as TextStyle, - // Heading 2 / Title 2 — Sans for exercise titles, program cards + HERO: { + fontFamily: FONT.SERIF, + fontSize: 34, + fontWeight: '700' as const, + lineHeight: 41, + letterSpacing: 0.37, + } as TextStyle, + + // Large Title — iOS native large title + LARGE_TITLE: { + fontSize: 34, + fontWeight: '700' as const, + lineHeight: 41, + letterSpacing: 0.37, + } as TextStyle, + + // Heading 1 + HEADING_1: { + fontSize: 28, + fontWeight: '700' as const, + lineHeight: 34, + letterSpacing: 0.36, + } as TextStyle, + + // Title 1 + TITLE_1: { + fontSize: 28, + fontWeight: '400' as const, + lineHeight: 34, + letterSpacing: 0.36, + } as TextStyle, + + // Heading 2 — exercise titles, program cards HEADING_2: { - fontFamily: FONT_FAMILY.SANS_SEMIBOLD, - fontSize: 18, - lineHeight: 24, - letterSpacing: 0, + fontSize: 22, + fontWeight: '700' as const, + lineHeight: 28, + letterSpacing: 0.35, } as TextStyle, TITLE_2: { - fontFamily: FONT_FAMILY.SANS_BOLD, fontSize: 22, + fontWeight: '400' as const, lineHeight: 28, letterSpacing: 0.35, } as TextStyle, TITLE_3: { - fontFamily: FONT.SANS_SEMIBOLD, fontSize: 20, + fontWeight: '400' as const, lineHeight: 25, letterSpacing: 0.38, } as TextStyle, // Headline HEADLINE: { - fontFamily: FONT.SANS_SEMIBOLD, fontSize: 17, + fontWeight: '600' as const, lineHeight: 22, letterSpacing: -0.41, } as TextStyle, - // Body — Sans + // Body BODY: { - fontFamily: FONT.SANS, - fontSize: 15, - lineHeight: 20, - letterSpacing: -0.24, + fontSize: 17, + fontWeight: '400' as const, + lineHeight: 22, + letterSpacing: -0.41, } as TextStyle, BODY_BOLD: { - fontFamily: FONT.SANS_SEMIBOLD, - fontSize: 15, - lineHeight: 20, - letterSpacing: -0.24, + fontSize: 17, + fontWeight: '600' as const, + lineHeight: 22, + letterSpacing: -0.41, } as TextStyle, // Callout CALLOUT: { - fontFamily: FONT.SANS, fontSize: 16, + fontWeight: '400' as const, lineHeight: 21, letterSpacing: -0.32, } as TextStyle, // Subheadline SUBHEADLINE: { - fontFamily: FONT.SANS, fontSize: 15, + fontWeight: '400' as const, lineHeight: 20, letterSpacing: -0.24, } as TextStyle, - // Label — Mono for tags, metadata, uppercase + // Label — small uppercase for tags, metadata LABEL: { - fontFamily: FONT.MONO_MEDIUM, - fontSize: 11, + fontSize: 12, + fontWeight: '500' as const, lineHeight: 16, - letterSpacing: 0.08, + letterSpacing: 0.5, textTransform: 'uppercase' as const, } as TextStyle, // Footnote FOOTNOTE: { - fontFamily: FONT.SANS, fontSize: 13, + fontWeight: '400' as const, lineHeight: 18, letterSpacing: -0.08, } as TextStyle, // Caption CAPTION_1: { - fontFamily: FONT.SANS, fontSize: 12, + fontWeight: '400' as const, lineHeight: 16, letterSpacing: 0, } as TextStyle, CAPTION_2: { - fontFamily: FONT.SANS, fontSize: 11, + fontWeight: '400' as const, lineHeight: 13, letterSpacing: 0.07, } as TextStyle, // ───────────────────────────────────────────────────────────────────────── - // SPECIAL: TIMER — Mono, readable from 2 meters + // SPECIAL: TIMER — Monospaced, readable from 2 meters // ───────────────────────────────────────────────────────────────────────── TIMER_NUMBER: { - fontFamily: FONT.MONO_MEDIUM, + fontFamily: FONT.MONO, fontSize: 88, + fontWeight: '300' as const, lineHeight: 88, letterSpacing: -2, fontVariant: ['tabular-nums'], } as TextStyle, TIMER_NUMBER_COMPACT: { - fontFamily: FONT.MONO_MEDIUM, + fontFamily: FONT.MONO, fontSize: 72, + fontWeight: '300' as const, lineHeight: 72, letterSpacing: -1.5, fontVariant: ['tabular-nums'], } as TextStyle, TIMER_PHASE: { - fontFamily: FONT.MONO_MEDIUM, fontSize: 13, + fontWeight: '600' as const, lineHeight: 18, - letterSpacing: 0.15, + letterSpacing: 1.5, textTransform: 'uppercase' as const, } as TextStyle, TIMER_ROUND: { - fontFamily: FONT.MONO_MEDIUM, + fontFamily: FONT.MONO, fontSize: 17, + fontWeight: '500' as const, lineHeight: 22, letterSpacing: 0, fontVariant: ['tabular-nums'], } as TextStyle, EXERCISE_NAME: { - fontFamily: FONT.SANS_BOLD, fontSize: 28, + fontWeight: '700' as const, lineHeight: 34, letterSpacing: 0.36, textAlign: 'center' as const, } as TextStyle, // ───────────────────────────────────────────────────────────────────────── - // SPECIAL: BUTTON — Sans + // SPECIAL: BUTTON // ───────────────────────────────────────────────────────────────────────── BUTTON_LARGE: { - fontFamily: FONT.SANS_SEMIBOLD, - fontSize: 15, - lineHeight: 20, - letterSpacing: 0.5, + fontSize: 17, + fontWeight: '600' as const, + lineHeight: 22, + letterSpacing: -0.41, } as TextStyle, BUTTON_MEDIUM: { - fontFamily: FONT.SANS_SEMIBOLD, fontSize: 15, + fontWeight: '600' as const, lineHeight: 20, - letterSpacing: 0.3, + letterSpacing: -0.24, } as TextStyle, BUTTON_SMALL: { - fontFamily: FONT.SANS_SEMIBOLD, fontSize: 14, + fontWeight: '600' as const, lineHeight: 18, - letterSpacing: 0.2, + letterSpacing: -0.15, } as TextStyle, // ───────────────────────────────────────────────────────────────────────── @@ -249,55 +269,56 @@ export const TYPOGRAPHY = { // ───────────────────────────────────────────────────────────────────────── CARD_TITLE: { - fontFamily: FONT.SANS_SEMIBOLD, fontSize: 17, + fontWeight: '600' as const, lineHeight: 22, letterSpacing: -0.41, } as TextStyle, CARD_SUBTITLE: { - fontFamily: FONT.SANS, - fontSize: 14, - lineHeight: 18, - letterSpacing: -0.15, + fontSize: 15, + fontWeight: '400' as const, + lineHeight: 20, + letterSpacing: -0.24, } as TextStyle, CARD_METADATA: { - fontFamily: FONT.SANS_MEDIUM, fontSize: 13, - lineHeight: 16, - letterSpacing: 0, + fontWeight: '500' as const, + lineHeight: 18, + letterSpacing: -0.08, } as TextStyle, // ───────────────────────────────────────────────────────────────────────── - // SPECIAL: STATS — Mono + // SPECIAL: STATS — Monospaced numerals // ───────────────────────────────────────────────────────────────────────── STAT_VALUE: { - fontFamily: FONT.MONO_MEDIUM, - fontSize: 32, - lineHeight: 38, + fontFamily: FONT.MONO, + fontSize: 34, + fontWeight: '300' as const, + lineHeight: 41, letterSpacing: -0.5, fontVariant: ['tabular-nums'], } as TextStyle, STAT_LABEL: { - fontFamily: FONT.MONO_MEDIUM, - fontSize: 11, + fontSize: 12, + fontWeight: '500' as const, lineHeight: 16, - letterSpacing: 0.08, + letterSpacing: 0.5, textTransform: 'uppercase' as const, } as TextStyle, // ───────────────────────────────────────────────────────────────────────── - // SPECIAL: OVERLINE / SECTION HEADER — Mono + // SPECIAL: OVERLINE / SECTION HEADER // ───────────────────────────────────────────────────────────────────────── OVERLINE: { - fontFamily: FONT.MONO_MEDIUM, - fontSize: 11, + fontSize: 12, + fontWeight: '500' as const, lineHeight: 16, - letterSpacing: 0.08, + letterSpacing: 0.5, textTransform: 'uppercase' as const, } as TextStyle, diff --git a/src/shared/theme/colors.dark.ts b/src/shared/theme/colors.dark.ts index 850a59b..46e78a8 100644 --- a/src/shared/theme/colors.dark.ts +++ b/src/shared/theme/colors.dark.ts @@ -1,49 +1,51 @@ /** - * Dark Medical theme palette - * Navy backgrounds, green actions, no glass, no shadows + * Dark Premium theme palette + * Refined navy backgrounds, green actions, native iOS feel */ import type { ThemeColors } from './types' +import { NAVY, TEXT as TEXT_COLORS, BORDER_COLORS, GREEN, ORANGE } from '../constants/colors' export const darkColors: ThemeColors = { bg: { - base: '#0D1B2A', // navy-900 - surface: '#112240', // navy-800 - elevated: '#1A3050', // navy-700 - overlay1: 'rgba(168,178,216,0.06)', - overlay2: 'rgba(168,178,216,0.10)', - overlay3: 'rgba(168,178,216,0.15)', + base: NAVY[900], + surface: NAVY[800], + elevated: NAVY[700], + overlay1: 'rgba(150,164,190,0.05)', + overlay2: 'rgba(150,164,190,0.08)', + overlay3: 'rgba(150,164,190,0.12)', scrim: 'rgba(0,0,0,0.6)', }, text: { - primary: '#E6F1FF', // white-100 - secondary: '#A8B2D8', // slate-300 - tertiary: '#8892B0', // slate-400 - muted: '#8892B0', - hint: '#8892B0', - disabled: '#3A3A3C', + primary: TEXT_COLORS.PRIMARY, + secondary: TEXT_COLORS.SECONDARY, + tertiary: TEXT_COLORS.TERTIARY, + muted: TEXT_COLORS.MUTED, + hint: TEXT_COLORS.HINT, + disabled: TEXT_COLORS.DISABLED, }, surface: { default: { - backgroundColor: '#112240', - borderColor: 'rgba(168,178,216,0.15)', + backgroundColor: NAVY[800], + borderColor: BORDER_COLORS.DIM, borderWidth: 1, }, accent: { - backgroundColor: 'rgba(0,200,150,0.05)', - borderColor: 'rgba(0,200,150,0.35)', + backgroundColor: GREEN.DIM, + borderColor: GREEN.BORDER, borderWidth: 1.5, }, tip: { - backgroundColor: 'rgba(255,138,92,0.12)', - borderColor: '#FF8A5C', + backgroundColor: ORANGE.DIM, + borderColor: ORANGE[500], borderWidth: 1, }, }, border: { - dim: 'rgba(168,178,216,0.15)', - hover: 'rgba(168,178,216,0.25)', - brand: 'rgba(0,200,150,0.35)', + dim: BORDER_COLORS.DIM, + hover: BORDER_COLORS.HOVER, + brand: BORDER_COLORS.BRAND, + separator: BORDER_COLORS.SEPARATOR, }, gradients: { videoOverlay: ['transparent', 'rgba(0,0,0,0.8)'], diff --git a/src/shared/theme/types.ts b/src/shared/theme/types.ts index 8a03acf..fc8ba55 100644 --- a/src/shared/theme/types.ts +++ b/src/shared/theme/types.ts @@ -1,6 +1,6 @@ /** * Theme type definitions - * Dark Medical — no glass, no shadows + * Dark Premium — refined navy, native iOS feel */ export interface SurfaceStyle { @@ -36,6 +36,7 @@ export interface ThemeColors { dim: string hover: string brand: string + separator: string } gradients: { videoOverlay: readonly string[]