feat: onboarding flow (6 screens) + audio engine + design system

Onboarding:
- 6-screen flow: Problem → Empathy → Solution → Wow → Personalization → Paywall
- useOnboarding hook with Zustand + AsyncStorage persistence
- MiniTimerDemo with live 20s timer + haptics
- Auto-redirect for first-time users
- Mock RevenueCat for dev testing

Audio:
- useAudioEngine hook with expo-av
- Phase sounds (count_3/2/1, beep, bell, fanfare)
- Placeholder music tracks

Design System:
- Typography component + constants
- GlassView component
- Spacing, shadows, animations, borderRadius constants
- Extended color palette (phase gradients, glass, surfaces)

Timer:
- Fix: handle 0-duration phases (immediate advance)
- Enhanced TimerDisplay with phase gradients

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Millian Lamiaux
2026-02-17 21:52:23 +01:00
parent 31bdb1586f
commit fa189fe72e
55 changed files with 3361 additions and 320 deletions

View File

@@ -0,0 +1,51 @@
import { Easing } from 'react-native'
export const DURATION = {
SNAP: 60,
QUICK: 120,
FAST: 300,
NORMAL: 400,
SLOW: 600,
XSLOW: 1000,
BREATH: 1800,
} as const
export const EASING = {
STANDARD: Easing.inOut(Easing.ease),
DECELERATE: Easing.out(Easing.ease),
LINEAR: Easing.linear,
} as const
export const SPRING = {
BOUNCY: { tension: 50, friction: 7 },
} as const
export const ANIMATION = {
PULSE_UP: {
toValue: 1.08,
duration: DURATION.SNAP,
useNativeDriver: true,
},
PULSE_DOWN: {
toValue: 1,
duration: DURATION.QUICK,
easing: EASING.DECELERATE,
useNativeDriver: true,
},
GRADIENT_CROSSFADE: {
toValue: 1,
duration: DURATION.SLOW,
easing: EASING.STANDARD,
useNativeDriver: true,
},
FADE_IN: {
toValue: 1,
duration: DURATION.NORMAL,
useNativeDriver: true,
},
BREATH_HALF: {
duration: DURATION.BREATH,
easing: EASING.STANDARD,
useNativeDriver: false,
},
} as const

View File

@@ -0,0 +1,11 @@
export const RADIUS = {
XS: 4,
SM: 6,
MD: 12,
LG: 16,
XL: 20,
'2XL': 28,
'3XL': 32,
'4XL': 36,
FULL: 9999,
} as const

View File

@@ -5,3 +5,72 @@ export const PHASE_COLORS = {
REST: '#3B82F6',
COMPLETE: '#22C55E',
} as const
export const PHASE_GRADIENTS = {
IDLE: ['#0A0A14', '#12101F', '#1E1E2E'] as const,
GET_READY: ['#451A03', '#92400E', '#D97706'] as const,
WORK: ['#450A0A', '#991B1B', '#EA580C'] as const,
REST: ['#0C1929', '#1E3A5F', '#2563EB'] as const,
COMPLETE: ['#052E16', '#166534', '#16A34A'] as const,
} as const
export const ACCENT = {
ORANGE: '#F97316',
ORANGE_GLOW: '#FB923C',
RED_HOT: '#EF4444',
GOLD: '#FBBF24',
WHITE: '#FFFFFF',
WHITE_DIM: 'rgba(255, 255, 255, 0.6)',
WHITE_FAINT: 'rgba(255, 255, 255, 0.15)',
} as const
export const BRAND = {
PRIMARY: '#F97316',
PRIMARY_LIGHT: '#FB923C',
SECONDARY: '#FBBF24',
DANGER: '#EF4444',
SUCCESS: '#22C55E',
INFO: '#3B82F6',
} as const
export const SURFACE = {
BASE: '#0A0A14',
RAISED: '#12101F',
ELEVATED: '#1E1E2E',
OVERLAY_LIGHT: 'rgba(255, 255, 255, 0.08)',
OVERLAY_MEDIUM: 'rgba(255, 255, 255, 0.15)',
OVERLAY_STRONG: 'rgba(255, 255, 255, 0.25)',
SCRIM: 'rgba(0, 0, 0, 0.3)',
} as const
export const TEXT = {
PRIMARY: '#FFFFFF',
SECONDARY: 'rgba(255, 255, 255, 0.85)',
TERTIARY: 'rgba(255, 255, 255, 0.75)',
MUTED: 'rgba(255, 255, 255, 0.6)',
HINT: 'rgba(255, 255, 255, 0.45)',
DISABLED: 'rgba(255, 255, 255, 0.15)',
} as const
export const BORDER = {
SUBTLE: 'rgba(255, 255, 255, 0.05)',
LIGHT: 'rgba(255, 255, 255, 0.06)',
MEDIUM: 'rgba(255, 255, 255, 0.15)',
STRONG: 'rgba(255, 255, 255, 0.3)',
} as const
export const APP_GRADIENTS = {
HOME: ['#0A0A14', '#1A0E2E', '#2D1810'] as const,
} as const
export const GLASS = {
FILL: 'rgba(255, 255, 255, 0.03)',
FILL_MEDIUM: 'rgba(255, 255, 255, 0.06)',
FILL_STRONG: 'rgba(255, 255, 255, 0.10)',
BORDER: 'rgba(255, 255, 255, 0.08)',
BORDER_TOP: 'rgba(255, 255, 255, 0.15)',
BLUR_LIGHT: 15,
BLUR_MEDIUM: 25,
BLUR_HEAVY: 40,
BLUR_ATMOSPHERE: 60,
} as const

View File

@@ -0,0 +1,16 @@
export {
PHASE_COLORS,
PHASE_GRADIENTS,
ACCENT,
BRAND,
SURFACE,
TEXT,
BORDER,
APP_GRADIENTS,
GLASS,
} from './colors'
export { TYPOGRAPHY } from './typography'
export { SPACING, LAYOUT } from './spacing'
export { SHADOW, TEXT_SHADOW } from './shadows'
export { RADIUS } from './borderRadius'
export { DURATION, EASING, SPRING, ANIMATION } from './animations'

View File

@@ -0,0 +1,44 @@
import type { TextStyle, ViewStyle } from 'react-native'
export const SHADOW = {
BRAND_GLOW: {
shadowColor: '#F97316',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.5,
shadowRadius: 20,
elevation: 12,
} as ViewStyle,
WHITE_GLOW: {
shadowColor: '#FFFFFF',
shadowOffset: { width: 0, height: 0 },
shadowOpacity: 0.8,
shadowRadius: 6,
} as ViewStyle,
} as const
export const TEXT_SHADOW = {
BRAND: {
textShadowColor: 'rgba(249, 115, 22, 0.5)',
textShadowOffset: { width: 0, height: 0 },
textShadowRadius: 30,
} as TextStyle,
WHITE_SOFT: {
textShadowColor: 'rgba(255, 255, 255, 0.25)',
textShadowOffset: { width: 0, height: 0 },
textShadowRadius: 30,
} as TextStyle,
WHITE_MEDIUM: {
textShadowColor: 'rgba(255, 255, 255, 0.3)',
textShadowOffset: { width: 0, height: 0 },
textShadowRadius: 20,
} as TextStyle,
DANGER: {
textShadowColor: 'rgba(239, 68, 68, 0.6)',
textShadowOffset: { width: 0, height: 0 },
textShadowRadius: 40,
} as TextStyle,
} as const

View File

@@ -0,0 +1,25 @@
export const SPACING = {
0: 0,
0.5: 2,
1: 4,
1.5: 6,
2: 8,
2.5: 10,
3: 12,
3.5: 14,
4: 16,
5: 20,
6: 24,
8: 32,
10: 40,
12: 48,
14: 56,
15: 60,
} as const
export const LAYOUT = {
PAGE_HORIZONTAL: 24,
SECTION_GAP: 40,
INLINE_GAP: 16,
CONTROLS_GAP: 32,
} as const

View File

@@ -0,0 +1,73 @@
import type { TextStyle } from 'react-native'
export const TYPOGRAPHY = {
countdown: {
fontSize: 140,
fontWeight: '900',
fontVariant: ['tabular-nums'],
} as TextStyle,
timeDisplay: {
fontSize: 72,
fontWeight: '900',
fontVariant: ['tabular-nums'],
} as TextStyle,
brandTitle: {
fontSize: 56,
fontWeight: '900',
letterSpacing: 12,
} as TextStyle,
displayLarge: {
fontSize: 42,
fontWeight: '900',
letterSpacing: 4,
} as TextStyle,
displaySmall: {
fontSize: 32,
fontWeight: '900',
letterSpacing: 20,
} as TextStyle,
buttonHero: {
fontSize: 30,
fontWeight: '900',
letterSpacing: 5,
} as TextStyle,
heading: {
fontSize: 24,
fontWeight: '800',
} as TextStyle,
buttonMedium: {
fontSize: 20,
fontWeight: '700',
letterSpacing: 2,
} as TextStyle,
body: {
fontSize: 18,
fontWeight: '700',
} as TextStyle,
caption: {
fontSize: 16,
fontWeight: '500',
} as TextStyle,
label: {
fontSize: 15,
fontWeight: '800',
letterSpacing: 1,
} as TextStyle,
overline: {
fontSize: 11,
fontWeight: '600',
textTransform: 'uppercase',
letterSpacing: 1,
} as TextStyle,
} as const