New look: slate-indigo panels, pink accent, Poppins, and a gradient card for your song

Inspired by a dashboard design the user shared.
- App: new palette and fonts, borderless rounded panels with soft shadows, a pink-to-blue
  gradient card for your own song (text and controls switch to white on it), flat cards for
  friends, a 'Hi Chris, welcome back' header, an outline bell, and a pink pill on the active tab.
- Website: the same look, plus a wide-screen layout with a sidebar, the page in a rounded panel,
  and a right column with your profile, recent activity and recent plays. Phones keep the top
  bar and bottom tabs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011Y2iUsKEWDuRGjK5FPmcUP
This commit is contained in:
2026-10-15 15:01:20 -04:00
co-authored by Claude Opus 5.5
parent 2025225fe4
commit 864ab88627
43 changed files with 648 additions and 394 deletions
@@ -1,36 +1,15 @@
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 { LinearGradient } from 'expo-linear-gradient';
import { StyleSheet } from 'react-native';
import { colors } from '@/lib/theme';
const AnimatedImage = Animated.createAnimatedComponent(Image);
/** Two soft colour blobs drifting slowly behind the content, so screens never feel static. */
/** The page behind everything: flat slate, a touch lighter at the top. */
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>
<LinearGradient
colors={['#32304e', colors.bg, colors.bg]}
locations={[0, 0.45, 1]}
style={[StyleSheet.absoluteFill, { backgroundColor: colors.bg }]}
pointerEvents="none"
/>
);
}
const styles = StyleSheet.create({
blob: { position: 'absolute', top: 0, left: 0, opacity: 0.45 },
});