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
+4 -3
View File
@@ -5,10 +5,11 @@ export default function TabsLayout() {
return (
<NativeTabs
backgroundColor={colors.bg}
indicatorColor={colors.surface2}
// The active tab gets a pink pill, like the reference's sidebar.
indicatorColor={colors.accentSoft}
// Android defaults icons and labels to black, which vanishes on the dark bar.
iconColor={{ default: colors.muted, selected: colors.text }}
labelStyle={{ default: { color: colors.muted }, selected: { color: colors.text } }}
iconColor={{ default: colors.muted, selected: colors.accent }}
labelStyle={{ default: { color: colors.muted }, selected: { color: colors.accent } }}
labelVisibilityMode="labeled"
tintColor={colors.accent}>
<NativeTabs.Trigger name="index">
+20 -5
View File
@@ -95,7 +95,19 @@ export default function FeedScreen() {
ListHeaderComponent={
<View style={styles.header}>
<View style={styles.topBar}>
<Text style={styles.brand}>musicshare</Text>
{me ? (
<Pressable style={styles.greeting} onPress={() => router.push('/profile')} accessibilityRole="link">
<Avatar user={me} size={44} />
<View style={{ flexShrink: 1 }}>
<Text style={styles.hi} numberOfLines={1}>
Hi {me.displayName ?? me.username},
</Text>
<Text style={styles.welcome}>Welcome back</Text>
</View>
</Pressable>
) : (
<Text style={styles.brand}>musicshare</Text>
)}
<ActivityBell />
</View>
<UpdateBanner />
@@ -115,7 +127,7 @@ export default function FeedScreen() {
)}
{mine?.nowPlaying && (
<View style={{ gap: space.sm }}>
<Text style={styles.sectionLabel}>YOU</Text>
<Text style={styles.sectionLabel}>Your song</Text>
<NowPlayingCard np={mine.nowPlaying} preferred={me?.preferredService} showUser={false} />
</View>
)}
@@ -143,7 +155,7 @@ export default function FeedScreen() {
ListFooterComponent={
quiet.length > 0 ? (
<View style={{ gap: space.sm, marginTop: space.md }}>
<Text style={styles.sectionLabel}>FOLLOWING · NOTHING PLAYED YET</Text>
<Text style={styles.sectionLabel}>Following · nothing played yet</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={styles.quiet}>
{quiet.map((u) => (
<Pressable key={u.id} onPress={() => router.push(`/u/${u.username}`)} style={styles.quietPerson} accessibilityLabel={u.displayName ?? u.username}>
@@ -183,8 +195,11 @@ const styles = StyleSheet.create({
header: { gap: space.md, marginBottom: space.xs },
topBar: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
brand: { color: colors.text, fontFamily: fonts.display, fontSize: 22, letterSpacing: -0.4 },
sectionLabel: { color: colors.muted, fontSize: 12, fontWeight: '700', letterSpacing: 0.8 },
greeting: { flexDirection: 'row', alignItems: 'center', gap: space.md, flexShrink: 1 },
hi: { color: colors.text, fontFamily: fonts.display, fontSize: 19 },
welcome: { color: colors.muted, fontFamily: fonts.body, fontSize: 13 },
sectionLabel: { color: colors.muted, fontSize: 13, fontFamily: fonts.bodyMedium },
quiet: { gap: space.lg, paddingVertical: space.xs },
quietPerson: { width: 64, alignItems: 'center', gap: 4 },
quietName: { color: colors.text, fontSize: 12, textAlign: 'center' },
quietName: { color: colors.text, fontSize: 11.5, fontFamily: fonts.body, textAlign: 'center' },
});
+3 -3
View File
@@ -10,7 +10,7 @@ import { PersonRow, emptyFollowText, useFollowList } from '@/components/FollowLi
import { Body, Button, Chip, Field, Row, Title } from '@/components/ui';
import { useSession } from '@/lib/session';
import { useDebounced } from '@/lib/useDebounced';
import { colors, space } from '@/lib/theme';
import { colors, fonts, space } from '@/lib/theme';
/** Friends: who you follow and who follows you, plus search and QR codes to find new people. */
export default function FriendsScreen() {
@@ -95,6 +95,6 @@ const styles = StyleSheet.create({
safe: { flex: 1, backgroundColor: colors.bg },
list: { padding: space.lg, gap: space.md },
row: { flexDirection: 'row', alignItems: 'center', gap: space.md, paddingVertical: space.sm },
name: { color: colors.text, fontWeight: '700', fontSize: 15 },
handle: { color: colors.muted, fontSize: 13 },
name: { color: colors.text, fontFamily: fonts.display, fontSize: 15 },
handle: { color: colors.muted, fontSize: 13, fontFamily: fonts.body },
});
+3 -3
View File
@@ -33,7 +33,7 @@ import { openReaderSettings, useReader } from '@/lib/nowPlayingReader';
import { NTFY_URL, disablePush, enablePush, pushAvailable, usePushStatus } from '@/lib/push';
import { useSession } from '@/lib/session';
import { checkForUpdate, currentVersion, installUpdate, updaterAvailable, useUpdateState } from '@/lib/updates';
import { colors, radius, space } from '@/lib/theme';
import { colors, fonts, radius, space } from '@/lib/theme';
export default function SettingsScreen() {
return (
@@ -670,7 +670,7 @@ function Account() {
}
const styles = StyleSheet.create({
small: { fontSize: 13 },
small: { fontSize: 13, fontFamily: fonts.body },
avatarRow: { flexDirection: 'row', alignItems: 'center', gap: space.lg },
connection: { flexDirection: 'row', alignItems: 'center', gap: space.md },
connectionColumn: {
@@ -680,6 +680,6 @@ const styles = StyleSheet.create({
paddingTop: space.md,
},
result: { flexDirection: 'row', alignItems: 'center', gap: space.md, paddingVertical: space.xs },
resultText: { color: colors.text, flex: 1, fontSize: 14 },
resultText: { color: colors.text, flex: 1, fontSize: 14, fontFamily: fonts.body },
thumb: { width: 40, height: 40, borderRadius: radius.sm },
});
+2 -2
View File
@@ -1,4 +1,4 @@
import { SpaceGrotesk_500Medium, SpaceGrotesk_700Bold, useFonts } from '@expo-google-fonts/space-grotesk';
import { Poppins_400Regular, Poppins_500Medium, Poppins_600SemiBold, useFonts } from '@expo-google-fonts/poppins';
import { DarkTheme, Stack, ThemeProvider } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import { StatusBar } from 'expo-status-bar';
@@ -37,7 +37,7 @@ function AppNavigator() {
const { status, me, retry } = useSession();
const signedIn = status === 'signedIn';
const onboarded = Boolean(me?.onboardedAt);
const [fontsLoaded, fontError] = useFonts({ SpaceGrotesk_500Medium, SpaceGrotesk_700Bold });
const [fontsLoaded, fontError] = useFonts({ Poppins_400Regular, Poppins_500Medium, Poppins_600SemiBold });
const ready = status !== 'loading' && (fontsLoaded || Boolean(fontError));
useEffect(() => {
+2 -2
View File
@@ -6,7 +6,7 @@ import { Avatar } from '@/components/Avatar';
import { Body, Button } from '@/components/ui';
import { api } from '@/lib/api';
import { showError } from '@/lib/errors';
import { colors, space } from '@/lib/theme';
import { colors, fonts, space } from '@/lib/theme';
export default function BlockedScreen() {
const { data: blocked = [], mutate, isLoading } = useSWR<UserSummaryDTO[]>('/me/blocks');
@@ -45,5 +45,5 @@ const styles = StyleSheet.create({
list: { padding: space.lg, gap: space.md },
row: { flexDirection: 'row', alignItems: 'center', gap: space.md },
person: { flexDirection: 'row', alignItems: 'center', gap: space.md },
name: { color: colors.text, fontSize: 15, fontWeight: '600', flexShrink: 1 },
name: { color: colors.text, fontSize: 15, fontFamily: fonts.bodyMedium, flexShrink: 1 },
});
+3 -3
View File
@@ -8,7 +8,7 @@ import { notificationPath, notificationText, timeAgo, type NotificationDTO, type
import { Avatar } from '@/components/Avatar';
import { Body } from '@/components/ui';
import { api } from '@/lib/api';
import { colors, space } from '@/lib/theme';
import { colors, fonts, space } from '@/lib/theme';
/** Reactions, new followers and songs sent to you, newest first. Opening it marks them read. */
export default function NotificationsScreen() {
@@ -77,8 +77,8 @@ const styles = StyleSheet.create({
row: { flexDirection: 'row', gap: space.md, alignItems: 'center', padding: space.md, borderRadius: 14 },
unread: { backgroundColor: colors.surface2 },
text: { flex: 1, minWidth: 0, gap: 2 },
title: { color: colors.text, fontWeight: '700', fontSize: 15 },
body: { color: colors.text, opacity: 0.85, fontSize: 14 },
title: { color: colors.text, fontFamily: fonts.display, fontSize: 15 },
body: { color: colors.text, opacity: 0.85, fontSize: 14, fontFamily: fonts.body },
time: { color: colors.muted, fontSize: 12 },
art: { width: 44, height: 44, borderRadius: 8 },
});
+3 -3
View File
@@ -11,7 +11,7 @@ import { WEB_URL } from '@/lib/config';
import { showError } from '@/lib/errors';
import { successHaptic } from '@/lib/haptics';
import { useSession } from '@/lib/session';
import { colors, space } from '@/lib/theme';
import { colors, fonts, space } from '@/lib/theme';
import { useDebounced } from '@/lib/useDebounced';
/** Step 3: an empty feed is the fastest way to lose someone, so find friends now. */
@@ -90,6 +90,6 @@ export default function FriendsStep() {
const styles = StyleSheet.create({
row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: space.md },
who: { flexDirection: 'row', alignItems: 'center', gap: space.md, flexShrink: 1 },
name: { color: colors.text, fontWeight: '700', fontSize: 15 },
handle: { color: colors.muted, fontSize: 13 },
name: { color: colors.text, fontFamily: fonts.display, fontSize: 15 },
handle: { color: colors.muted, fontSize: 13, fontFamily: fonts.body },
});
+3 -3
View File
@@ -9,7 +9,7 @@ import { showError } from '@/lib/errors';
import { selectionHaptic } from '@/lib/haptics';
import { LISTEN_APPS, finishOnboarding } from '@/lib/onboarding';
import { useSession } from '@/lib/session';
import { colors, radius, space } from '@/lib/theme';
import { colors, fonts, radius, space } from '@/lib/theme';
/** Step 1: where do you listen? Sets where friends' songs open, and picks the right setup next. */
export default function ListenAppStep() {
@@ -69,7 +69,7 @@ const styles = StyleSheet.create({
justifyContent: 'center',
paddingHorizontal: space.md,
},
optionPicked: { borderColor: colors.accent, backgroundColor: 'rgba(124,77,255,0.18)' },
optionText: { color: colors.text, fontWeight: '600', fontSize: 15 },
optionPicked: { borderColor: colors.accent, backgroundColor: colors.accentSoft },
optionText: { color: colors.text, fontFamily: fonts.bodyMedium, fontSize: 15 },
optionTextPicked: { color: '#fff' },
});
+5 -5
View File
@@ -13,7 +13,7 @@ import { api } from '@/lib/api';
import { showError } from '@/lib/errors';
import { tapHaptic } from '@/lib/haptics';
import { useSession } from '@/lib/session';
import { colors, radius, space } from '@/lib/theme';
import { colors, fonts, radius, space } from '@/lib/theme';
/** One play and its comments: where the 💬 chip and comment notifications lead. */
export default function PlayScreen() {
@@ -163,9 +163,9 @@ const styles = StyleSheet.create({
header: { gap: space.md, marginBottom: space.sm },
playedBy: { flexDirection: 'row', alignItems: 'center', gap: space.sm },
comment: { flexDirection: 'row', gap: space.md, alignItems: 'flex-start' },
name: { color: colors.text, fontWeight: '700' },
muted: { color: colors.muted, fontSize: 13 },
body: { color: colors.text, fontSize: 15, lineHeight: 21 },
name: { color: colors.text, fontFamily: fonts.display },
muted: { color: colors.muted, fontSize: 13, fontFamily: fonts.body },
body: { color: colors.text, fontSize: 15, lineHeight: 21, fontFamily: fonts.body },
more: { color: colors.muted, fontSize: 18, paddingHorizontal: space.xs },
composer: {
flexDirection: 'row',
@@ -190,6 +190,6 @@ const styles = StyleSheet.create({
borderColor: colors.border,
},
send: { paddingHorizontal: space.md, paddingVertical: space.sm + 2, borderRadius: radius.pill, backgroundColor: colors.accent },
sendText: { color: '#fff', fontWeight: '700' },
sendText: { color: '#fff', fontFamily: fonts.display },
closed: { margin: space.lg, marginTop: 0 },
});
+3 -3
View File
@@ -7,7 +7,7 @@ import { Screen } from '@/components/Screen';
import { Body, Button } from '@/components/ui';
import { WEB_URL } from '@/lib/config';
import { useSession } from '@/lib/session';
import { colors, space } from '@/lib/theme';
import { colors, fonts, space } from '@/lib/theme';
/** "My code": friends scan it (in musicshare or any camera app) to open your profile. */
export default function MyQrScreen() {
@@ -40,8 +40,8 @@ export default function MyQrScreen() {
const styles = StyleSheet.create({
content: { flexGrow: 1, alignItems: 'center', justifyContent: 'center', gap: space.xl },
who: { alignItems: 'center', gap: space.xs },
name: { color: colors.text, fontSize: 22, fontWeight: '800' },
handle: { color: colors.muted, fontSize: 15 },
name: { color: colors.text, fontSize: 22, fontFamily: fonts.display },
handle: { color: colors.muted, fontSize: 15, fontFamily: fonts.body },
hint: { textAlign: 'center', maxWidth: 300 },
actions: { alignSelf: 'stretch', gap: space.sm },
});
+2 -2
View File
@@ -8,7 +8,7 @@ import { Body, Button, Title } from '@/components/ui';
import { FOSS_BUILD } from '@/lib/build';
import { decodeQrFromJpeg, pickSnapshotSize } from '@/lib/qrSnapshot';
import { useSession } from '@/lib/session';
import { colors, radius, space } from '@/lib/theme';
import { colors, fonts, radius, space } from '@/lib/theme';
/** Scan a friend's musicshare QR code and open their profile (with its Follow button). */
export default function ScanScreen() {
@@ -115,7 +115,7 @@ const styles = StyleSheet.create({
prompt: {
color: colors.accentText,
fontSize: 16,
fontWeight: '600',
fontFamily: fonts.bodyMedium,
textAlign: 'center',
backgroundColor: 'rgba(0,0,0,0.55)',
paddingHorizontal: space.md,
+5 -5
View File
@@ -10,7 +10,7 @@ import { api } from '@/lib/api';
import { showError } from '@/lib/errors';
import { successHaptic, tapHaptic } from '@/lib/haptics';
import { useSession } from '@/lib/session';
import { colors, radius, space } from '@/lib/theme';
import { colors, fonts, radius, space } from '@/lib/theme';
/** Send a song to some of your followers, with an optional note. */
export default function SendSongScreen() {
@@ -98,10 +98,10 @@ export default function SendSongScreen() {
const styles = StyleSheet.create({
list: { padding: space.lg, gap: space.sm, paddingBottom: space.xl * 2 },
song: { color: colors.text, fontSize: 17, fontWeight: '700' },
muted: { color: colors.muted, fontWeight: '400' },
song: { color: colors.text, fontSize: 17, fontFamily: fonts.display },
muted: { color: colors.muted, fontFamily: fonts.body },
person: { flexDirection: 'row', alignItems: 'center', gap: space.md, padding: space.sm, borderRadius: radius.md },
on: { backgroundColor: colors.surface2 },
name: { flex: 1, color: colors.text, fontSize: 15, fontWeight: '600' },
check: { color: colors.muted, fontSize: 18 },
name: { flex: 1, color: colors.text, fontSize: 15, fontFamily: fonts.bodyMedium },
check: { color: colors.muted, fontSize: 18, fontFamily: fonts.body },
});