Mobile: redesign, onboarding, QR codes, logo, Spotify setup
- Redesign matching the web: album-art backdrops, Reanimated equalizer, spinning live ring, drifting ambient background, gradient buttons with spring press feedback and haptics, Space Grotesk headings, animated feed (enter + layout transitions); fix invisible tab labels - Four-step onboarding for new accounts: which app you listen with (sets where songs open), the matching share setup, find friends (search, scan, invite), and a featured song. Skippable; stored server-side - QR codes: My code (share link) and an in-app scanner (camera only, no microphone permission) - Spotify setup via Last.fm or ListenBrainz with guided linking; Android shares Spotify automatically through the phone reader - New logo as app icon, adaptive/monochrome icon and splash - app.config.ts allows cleartext only for local http:// test builds Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { Image } from 'expo-image';
|
||||
import { useEffect } from 'react';
|
||||
import { StyleSheet, useWindowDimensions, View } from 'react-native';
|
||||
import Animated, { Easing, useAnimatedStyle, useSharedValue, withRepeat, withTiming } from 'react-native-reanimated';
|
||||
import { colors } from '@/lib/theme';
|
||||
|
||||
const AnimatedImage = Animated.createAnimatedComponent(Image);
|
||||
|
||||
/** Two soft colour blobs drifting slowly behind the content, so screens never feel static. */
|
||||
export function AmbientBackground() {
|
||||
const { width, height } = useWindowDimensions();
|
||||
const t = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
t.set(withRepeat(withTiming(1, { duration: 18000, easing: Easing.inOut(Easing.sin) }), -1, true));
|
||||
}, [t]);
|
||||
|
||||
const size = Math.max(width, height) * 0.9;
|
||||
const purple = useAnimatedStyle(() => ({
|
||||
transform: [{ translateX: -size * 0.35 + t.value * width * 0.35 }, { translateY: -size * 0.3 + t.value * height * 0.12 }],
|
||||
}));
|
||||
const blue = useAnimatedStyle(() => ({
|
||||
transform: [{ translateX: width - size * 0.6 - t.value * width * 0.3 }, { translateY: height - size * 0.55 - t.value * height * 0.1 }],
|
||||
}));
|
||||
|
||||
return (
|
||||
<View style={[StyleSheet.absoluteFill, { backgroundColor: colors.bg }]} pointerEvents="none">
|
||||
<AnimatedImage source={require('@/assets/images/ambient-purple.png')} style={[styles.blob, { width: size, height: size }, purple]} />
|
||||
<AnimatedImage source={require('@/assets/images/ambient-blue.png')} style={[styles.blob, { width: size, height: size }, blue]} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
blob: { position: 'absolute', top: 0, left: 0, opacity: 0.45 },
|
||||
});
|
||||
Reference in New Issue
Block a user