Skip to content

Commit 76ba40b

Browse files
style: redesign onboarding with hero keywords and left-aligned layout
Replace empty centered text with editorial layout: - Large accent-colored keyword per slide (YOURS, MAGIC, CREATE, READY) - Animated accent line separator - Left-aligned text for intentional, editorial feel - 4-stage stagger animation: keyword → line → title → description - Dots left-aligned to match content alignment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 3bd3d6c commit 76ba40b

2 files changed

Lines changed: 82 additions & 26 deletions

File tree

src/constants/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,27 +312,27 @@ If asked about yourself, you can mention you're a local AI assistant that priori
312312
export const ONBOARDING_SLIDES = [
313313
{
314314
id: 'freedom',
315+
keyword: 'YOURS',
315316
title: 'Your AI.\nNo Strings Attached.',
316317
description: 'No subscriptions, no sign-ups, no company reading your chats. An AI that lives on your device and answers to no one else.',
317-
icon: 'anchor',
318318
},
319319
{
320320
id: 'magic',
321+
keyword: 'MAGIC',
321322
title: 'Just Talk.\nIt Figures Out the Rest.',
322323
description: 'Describe an image \u2014 it creates one. Show it a photo \u2014 it understands. Attach a document \u2014 it reads it. One conversation, no modes, no friction.',
323-
icon: 'compass',
324324
},
325325
{
326326
id: 'create',
327+
keyword: 'CREATE',
327328
title: 'Say It Simply.\nGet Something Stunning.',
328329
description: 'Type \u201Cimagine a cat on the moon\u201D and watch your words become a vivid image in seconds. AI enhances your ideas automatically \u2014 no prompt engineering needed.',
329-
icon: 'sunrise',
330330
},
331331
{
332332
id: 'hardware',
333+
keyword: 'READY',
333334
title: 'Tuned for\nYour Hardware.',
334335
description: 'Accelerated for Metal, NPU, and Neural Engine. We\u2019ll recommend the perfect model for your phone \u2014 so it flies from the start.',
335-
icon: 'sliders',
336336
},
337337
];
338338

src/screens/OnboardingScreen.tsx

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ import ReanimatedAnimated, {
1111
useAnimatedStyle,
1212
withTiming,
1313
withDelay,
14+
Easing,
1415
} from 'react-native-reanimated';
1516
import { SafeAreaView } from 'react-native-safe-area-context';
1617
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
1718
import { Button } from '../components';
1819
import { useTheme, useThemedStyles } from '../theme';
1920
import type { ThemeColors, ThemeShadows } from '../theme';
20-
import { ONBOARDING_SLIDES, SPACING, TYPOGRAPHY } from '../constants';
21+
import { ONBOARDING_SLIDES, SPACING, TYPOGRAPHY, FONTS } from '../constants';
2122
import { useAppStore } from '../stores';
2223
import { RootStackParamList } from '../navigation/types';
2324

@@ -27,38 +28,61 @@ type OnboardingScreenProps = {
2728

2829
const { width } = Dimensions.get('window');
2930

30-
/** Animated slide with staggered entrance for title and description */
31+
/** Animated slide with staggered entrance: keyword → title description */
3132
const SlideContent: React.FC<{
3233
item: typeof ONBOARDING_SLIDES[0];
3334
isActive: boolean;
3435
styles: ReturnType<typeof createStyles>;
36+
accentColor: string;
3537
}> = ({
3638
item,
3739
isActive,
3840
styles,
41+
accentColor,
3942
}) => {
43+
const keywordOpacity = useSharedValue(0);
44+
const keywordTranslateY = useSharedValue(24);
4045
const titleOpacity = useSharedValue(0);
4146
const titleTranslateY = useSharedValue(16);
4247
const descOpacity = useSharedValue(0);
43-
const descTranslateY = useSharedValue(16);
48+
const descTranslateY = useSharedValue(12);
49+
const lineWidth = useSharedValue(0);
4450

4551
useEffect(() => {
4652
if (isActive) {
4753
// Reset
54+
keywordOpacity.value = 0;
55+
keywordTranslateY.value = 24;
4856
titleOpacity.value = 0;
4957
titleTranslateY.value = 16;
5058
descOpacity.value = 0;
51-
descTranslateY.value = 16;
59+
descTranslateY.value = 12;
60+
lineWidth.value = 0;
5261

53-
// Stagger in
54-
titleOpacity.value = withTiming(1, { duration: 400 });
55-
titleTranslateY.value = withTiming(0, { duration: 400 });
56-
descOpacity.value = withDelay(200, withTiming(1, { duration: 400 }));
57-
descTranslateY.value = withDelay(200, withTiming(0, { duration: 400 }));
62+
const ease = Easing.out(Easing.cubic);
63+
64+
// Stagger: keyword → line → title → description
65+
keywordOpacity.value = withTiming(1, { duration: 500, easing: ease });
66+
keywordTranslateY.value = withTiming(0, { duration: 500, easing: ease });
67+
lineWidth.value = withDelay(250, withTiming(1, { duration: 400, easing: ease }));
68+
titleOpacity.value = withDelay(350, withTiming(1, { duration: 400, easing: ease }));
69+
titleTranslateY.value = withDelay(350, withTiming(0, { duration: 400, easing: ease }));
70+
descOpacity.value = withDelay(550, withTiming(1, { duration: 400, easing: ease }));
71+
descTranslateY.value = withDelay(550, withTiming(0, { duration: 400, easing: ease }));
5872
}
5973
// eslint-disable-next-line react-hooks/exhaustive-deps
6074
}, [isActive]);
6175

76+
const keywordStyle = useAnimatedStyle(() => ({
77+
opacity: keywordOpacity.value,
78+
transform: [{ translateY: keywordTranslateY.value }],
79+
}));
80+
81+
const lineStyle = useAnimatedStyle(() => ({
82+
transform: [{ scaleX: lineWidth.value }],
83+
opacity: lineWidth.value,
84+
}));
85+
6286
const titleStyle = useAnimatedStyle(() => ({
6387
opacity: titleOpacity.value,
6488
transform: [{ translateY: titleTranslateY.value }],
@@ -71,12 +95,27 @@ const SlideContent: React.FC<{
7195

7296
return (
7397
<View style={styles.slide}>
74-
<ReanimatedAnimated.View style={titleStyle}>
75-
<Text style={styles.title}>{item.title}</Text>
76-
</ReanimatedAnimated.View>
77-
<ReanimatedAnimated.View style={descStyle}>
78-
<Text style={styles.description}>{item.description}</Text>
79-
</ReanimatedAnimated.View>
98+
<View style={styles.slideInner}>
99+
{/* Hero keyword */}
100+
<ReanimatedAnimated.View style={keywordStyle}>
101+
<Text style={[styles.keyword, { color: accentColor }]}>
102+
{item.keyword}
103+
</Text>
104+
</ReanimatedAnimated.View>
105+
106+
{/* Accent line */}
107+
<ReanimatedAnimated.View style={[styles.accentLine, { backgroundColor: accentColor }, lineStyle]} />
108+
109+
{/* Title */}
110+
<ReanimatedAnimated.View style={titleStyle}>
111+
<Text style={styles.title}>{item.title}</Text>
112+
</ReanimatedAnimated.View>
113+
114+
{/* Description */}
115+
<ReanimatedAnimated.View style={descStyle}>
116+
<Text style={styles.description}>{item.description}</Text>
117+
</ReanimatedAnimated.View>
118+
</View>
80119
</View>
81120
);
82121
};
@@ -112,7 +151,7 @@ export const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
112151
};
113152

114153
const renderSlide = ({ item, index }: { item: typeof ONBOARDING_SLIDES[0]; index: number }) => (
115-
<SlideContent item={item} isActive={currentIndex === index} styles={styles} />
154+
<SlideContent item={item} isActive={currentIndex === index} styles={styles} accentColor={colors.primary} />
116155
);
117156

118157
const renderDots = () => (
@@ -210,33 +249,50 @@ const createStyles = (colors: ThemeColors, _shadows: ThemeShadows) => ({
210249
},
211250
slide: {
212251
width,
213-
paddingHorizontal: SPACING.xxl,
214252
justifyContent: 'center' as const,
215253
alignItems: 'center' as const,
216254
},
255+
slideInner: {
256+
paddingHorizontal: SPACING.xxl + 8,
257+
alignItems: 'flex-start' as const,
258+
width: '100%' as const,
259+
},
260+
keyword: {
261+
fontFamily: FONTS.mono,
262+
fontSize: 48,
263+
fontWeight: '200' as const,
264+
letterSpacing: 6,
265+
marginBottom: SPACING.lg,
266+
},
267+
accentLine: {
268+
height: 2,
269+
width: 48,
270+
marginBottom: SPACING.xl,
271+
},
217272
title: {
218273
...TYPOGRAPHY.h1,
219274
color: colors.text,
220-
textAlign: 'center' as const,
275+
textAlign: 'left' as const,
221276
marginBottom: SPACING.md,
222277
},
223278
description: {
224279
...TYPOGRAPHY.body,
225280
color: colors.textSecondary,
226-
textAlign: 'center' as const,
227-
lineHeight: 20,
281+
textAlign: 'left' as const,
282+
lineHeight: 22,
228283
},
229284
dotsContainer: {
230285
flexDirection: 'row' as const,
231-
justifyContent: 'center' as const,
286+
justifyContent: 'flex-start' as const,
232287
alignItems: 'center' as const,
233288
marginVertical: SPACING.xl,
289+
paddingHorizontal: SPACING.xxl + 8,
234290
},
235291
dot: {
236292
height: 8,
237293
borderRadius: 4,
238294
backgroundColor: colors.primary,
239-
marginHorizontal: SPACING.xs,
295+
marginRight: SPACING.xs,
240296
},
241297
footer: {
242298
paddingHorizontal: SPACING.xl,

0 commit comments

Comments
 (0)