diff --git a/apps/mobile/app.json b/apps/mobile/app.json index baef268..ba8e30d 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -31,7 +31,7 @@ [ "expo-splash-screen", { - "backgroundColor": "#0b0820", + "backgroundColor": "#2a2942", "image": "./assets/images/splash-icon.png", "imageWidth": 150 } diff --git a/apps/mobile/assets/images/ambient-blue.png b/apps/mobile/assets/images/ambient-blue.png deleted file mode 100644 index 1524ed1..0000000 Binary files a/apps/mobile/assets/images/ambient-blue.png and /dev/null differ diff --git a/apps/mobile/assets/images/ambient-purple.png b/apps/mobile/assets/images/ambient-purple.png deleted file mode 100644 index c603188..0000000 Binary files a/apps/mobile/assets/images/ambient-purple.png and /dev/null differ diff --git a/apps/mobile/package.json b/apps/mobile/package.json index a4f6d06..d5b5262 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -4,7 +4,7 @@ "version": "1.3.0", "license": "AGPL-3.0-or-later", "dependencies": { - "@expo-google-fonts/space-grotesk": "^0.4.1", + "@expo-google-fonts/poppins": "^0.4.1", "@expo/ui": "~57.0.21", "@musicshare/shared": "*", "expo": "~57.0.26", diff --git a/apps/mobile/src/app/(tabs)/_layout.tsx b/apps/mobile/src/app/(tabs)/_layout.tsx index a58f58f..46e5ebd 100644 --- a/apps/mobile/src/app/(tabs)/_layout.tsx +++ b/apps/mobile/src/app/(tabs)/_layout.tsx @@ -5,10 +5,11 @@ export default function TabsLayout() { return ( diff --git a/apps/mobile/src/app/(tabs)/index.tsx b/apps/mobile/src/app/(tabs)/index.tsx index 35a94c0..54477fd 100644 --- a/apps/mobile/src/app/(tabs)/index.tsx +++ b/apps/mobile/src/app/(tabs)/index.tsx @@ -95,7 +95,19 @@ export default function FeedScreen() { ListHeaderComponent={ - musicshare + {me ? ( + router.push('/profile')} accessibilityRole="link"> + + + + Hi {me.displayName ?? me.username}, + + Welcome back + + + ) : ( + musicshare + )} @@ -115,7 +127,7 @@ export default function FeedScreen() { )} {mine?.nowPlaying && ( - YOU + Your song )} @@ -143,7 +155,7 @@ export default function FeedScreen() { ListFooterComponent={ quiet.length > 0 ? ( - FOLLOWING · NOTHING PLAYED YET + Following · nothing played yet {quiet.map((u) => ( 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' }, }); diff --git a/apps/mobile/src/app/(tabs)/search.tsx b/apps/mobile/src/app/(tabs)/search.tsx index 7f7e0a3..d02d015 100644 --- a/apps/mobile/src/app/(tabs)/search.tsx +++ b/apps/mobile/src/app/(tabs)/search.tsx @@ -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 }, }); diff --git a/apps/mobile/src/app/(tabs)/settings.tsx b/apps/mobile/src/app/(tabs)/settings.tsx index ffbab8a..47a9947 100644 --- a/apps/mobile/src/app/(tabs)/settings.tsx +++ b/apps/mobile/src/app/(tabs)/settings.tsx @@ -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 }, }); diff --git a/apps/mobile/src/app/_layout.tsx b/apps/mobile/src/app/_layout.tsx index c627895..000f1db 100644 --- a/apps/mobile/src/app/_layout.tsx +++ b/apps/mobile/src/app/_layout.tsx @@ -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(() => { diff --git a/apps/mobile/src/app/blocked.tsx b/apps/mobile/src/app/blocked.tsx index f1483fb..a1091da 100644 --- a/apps/mobile/src/app/blocked.tsx +++ b/apps/mobile/src/app/blocked.tsx @@ -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('/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 }, }); diff --git a/apps/mobile/src/app/notifications.tsx b/apps/mobile/src/app/notifications.tsx index 956450e..8deb23e 100644 --- a/apps/mobile/src/app/notifications.tsx +++ b/apps/mobile/src/app/notifications.tsx @@ -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 }, }); diff --git a/apps/mobile/src/app/onboarding/friends.tsx b/apps/mobile/src/app/onboarding/friends.tsx index c49c7f0..2b99f4a 100644 --- a/apps/mobile/src/app/onboarding/friends.tsx +++ b/apps/mobile/src/app/onboarding/friends.tsx @@ -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 }, }); diff --git a/apps/mobile/src/app/onboarding/index.tsx b/apps/mobile/src/app/onboarding/index.tsx index 24930a9..e29e6e7 100644 --- a/apps/mobile/src/app/onboarding/index.tsx +++ b/apps/mobile/src/app/onboarding/index.tsx @@ -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' }, }); diff --git a/apps/mobile/src/app/play/[id].tsx b/apps/mobile/src/app/play/[id].tsx index 1307335..b563020 100644 --- a/apps/mobile/src/app/play/[id].tsx +++ b/apps/mobile/src/app/play/[id].tsx @@ -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 }, }); diff --git a/apps/mobile/src/app/qr.tsx b/apps/mobile/src/app/qr.tsx index 5af1cf3..a4f88fa 100644 --- a/apps/mobile/src/app/qr.tsx +++ b/apps/mobile/src/app/qr.tsx @@ -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 }, }); diff --git a/apps/mobile/src/app/scan.tsx b/apps/mobile/src/app/scan.tsx index fd4bc5a..696989b 100644 --- a/apps/mobile/src/app/scan.tsx +++ b/apps/mobile/src/app/scan.tsx @@ -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, diff --git a/apps/mobile/src/app/send/[trackId].tsx b/apps/mobile/src/app/send/[trackId].tsx index 992d56a..aa0c43f 100644 --- a/apps/mobile/src/app/send/[trackId].tsx +++ b/apps/mobile/src/app/send/[trackId].tsx @@ -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 }, }); diff --git a/apps/mobile/src/components/ActivityBell.tsx b/apps/mobile/src/components/ActivityBell.tsx index 586ef51..ede1d60 100644 --- a/apps/mobile/src/components/ActivityBell.tsx +++ b/apps/mobile/src/components/ActivityBell.tsx @@ -1,8 +1,9 @@ import { router } from 'expo-router'; import { Pressable, StyleSheet, Text, View } from 'react-native'; +import Svg, { Path } from 'react-native-svg'; import useSWR from 'swr'; import type { NotificationListDTO } from '@musicshare/shared'; -import { colors } from '@/lib/theme'; +import { colors, fonts } from '@/lib/theme'; /** Bell with the unread count; opens your notifications. */ export function ActivityBell() { @@ -15,7 +16,10 @@ export function ActivityBell() { accessibilityRole="button" accessibilityLabel={unread ? `Notifications, ${unread} new` : 'Notifications'} style={styles.bell}> - 🔔 + + + + {unread > 0 && ( {unread > 99 ? '99+' : unread} @@ -26,8 +30,7 @@ export function ActivityBell() { } const styles = StyleSheet.create({ - bell: { width: 38, height: 38, borderRadius: 19, alignItems: 'center', justifyContent: 'center', backgroundColor: colors.surface2 }, - icon: { fontSize: 18 }, + bell: { width: 42, height: 42, borderRadius: 21, alignItems: 'center', justifyContent: 'center', backgroundColor: colors.surface2 }, badge: { position: 'absolute', top: -2, @@ -36,9 +39,9 @@ const styles = StyleSheet.create({ height: 18, paddingHorizontal: 4, borderRadius: 9, - backgroundColor: colors.danger, + backgroundColor: colors.accent, alignItems: 'center', justifyContent: 'center', }, - badgeText: { color: '#fff', fontSize: 11, fontWeight: '700' }, + badgeText: { color: '#fff', fontSize: 11, fontFamily: fonts.display }, }); diff --git a/apps/mobile/src/components/AmbientBackground.tsx b/apps/mobile/src/components/AmbientBackground.tsx index 0649371..e53c2de 100644 --- a/apps/mobile/src/components/AmbientBackground.tsx +++ b/apps/mobile/src/components/AmbientBackground.tsx @@ -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 ( - - - - + ); } - -const styles = StyleSheet.create({ - blob: { position: 'absolute', top: 0, left: 0, opacity: 0.45 }, -}); diff --git a/apps/mobile/src/components/AuthForm.tsx b/apps/mobile/src/components/AuthForm.tsx index 8bd7efd..7fbd702 100644 --- a/apps/mobile/src/components/AuthForm.tsx +++ b/apps/mobile/src/components/AuthForm.tsx @@ -5,7 +5,7 @@ import { KeyboardAvoidingView, Linking, Platform, StyleSheet, Text, View } from import { api } from '@/lib/api'; import { WEB_URL } from '@/lib/config'; import { useSession } from '@/lib/session'; -import { colors, space } from '@/lib/theme'; +import { colors, fonts, space } from '@/lib/theme'; import { Screen } from './Screen'; import { Body, Button, Field, Title } from './ui'; @@ -106,7 +106,7 @@ export function AuthForm({ mode }: { mode: 'login' | 'register' }) { const styles = StyleSheet.create({ content: { flexGrow: 1, justifyContent: 'center' }, link: { color: colors.muted, textAlign: 'center', paddingVertical: space.sm }, - legal: { color: colors.muted, fontSize: 12, textAlign: 'center', lineHeight: 17 }, + legal: { color: colors.muted, fontSize: 12, textAlign: 'center', lineHeight: 17, fontFamily: fonts.body }, legalLink: { color: colors.text, textDecorationLine: 'underline' }, logo: { width: 76, height: 76, borderRadius: 20, marginBottom: space.sm }, }); diff --git a/apps/mobile/src/components/Avatar.tsx b/apps/mobile/src/components/Avatar.tsx index 06a771a..c3ff366 100644 --- a/apps/mobile/src/components/Avatar.tsx +++ b/apps/mobile/src/components/Avatar.tsx @@ -20,7 +20,7 @@ export function Avatar({ user, size = 40, live }: { user: UserSummaryDTO; size?: const face = user.avatarUrl ? ( ) : ( - + {(user.displayName ?? user.username).slice(0, 1).toUpperCase()} ); @@ -31,7 +31,7 @@ export function Avatar({ user, size = 40, live }: { user: UserSummaryDTO; size?: (`/tracks/${track.id}/lyrics`); const [full, setFull] = useState(false); - if (isLoading) return Finding lyrics…; - if (error) return Couldn't load lyrics right now.; - if (!data) return No lyrics found for this song.; - if (data.instrumental && !data.synced && !data.plain) return ♪ Instrumental; + if (isLoading) return Finding lyrics…; + if (error) return Couldn't load lyrics right now.; + if (!data) return No lyrics found for this song.; + if (data.instrumental && !data.synced && !data.plain) return ♪ Instrumental; const synced = data.synced; const live = Boolean(np?.isPlaying && playbackPosition(np, 0)); @@ -48,11 +50,11 @@ export function Lyrics({ track, np }: { track: TrackDTO; np?: NowPlayingDTO }) { {canToggle && ( - setFull((v) => !v)}> + setFull((v) => !v)}> {!full ? 'All lyrics' : synced && live ? 'Follow along' : 'Less'} )} - Linking.openURL(data.source.url)}> + Linking.openURL(data.source.url)}> Lyrics from {data.source.name} @@ -85,13 +87,13 @@ function FollowAlong({ lines, np }: { lines: LyricLine[]; np: NowPlayingDTO }) { const styles = StyleSheet.create({ wrap: { gap: space.sm }, - note: { color: colors.muted, fontSize: 13 }, - line: { color: colors.text, fontSize: 15, lineHeight: 22 }, + note: { color: colors.muted, fontSize: 13, fontFamily: fonts.body }, + line: { color: colors.text, fontSize: 14.5, lineHeight: 22, fontFamily: fonts.body }, plain: { opacity: 0.9 }, past: { opacity: 0.4 }, now: { fontFamily: fonts.display, fontSize: 17, lineHeight: 24 }, next: { opacity: 0.65 }, footer: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }, - toggle: { color: colors.accent2, fontSize: 13, fontWeight: '600' }, - source: { color: colors.muted, fontSize: 11, marginLeft: 'auto' }, + toggle: { color: colors.accent, fontSize: 13, fontFamily: fonts.bodyMedium }, + source: { color: colors.muted, fontSize: 11, fontFamily: fonts.body, marginLeft: 'auto' }, }); diff --git a/apps/mobile/src/components/NowPlayingCard.tsx b/apps/mobile/src/components/NowPlayingCard.tsx index 356e8dd..ffdfdd5 100644 --- a/apps/mobile/src/components/NowPlayingCard.tsx +++ b/apps/mobile/src/components/NowPlayingCard.tsx @@ -1,4 +1,3 @@ -import { Image } from 'expo-image'; import { LinearGradient } from 'expo-linear-gradient'; import { router } from 'expo-router'; import { useEffect, useRef, useState } from 'react'; @@ -13,7 +12,7 @@ import Animated, { } from 'react-native-reanimated'; import { SERVICE_LABELS, timeAgo, type NowPlayingDTO, type StreamingService } from '@musicshare/shared'; import { successHaptic, tapHaptic } from '@/lib/haptics'; -import { colors, radius, space } from '@/lib/theme'; +import { OnHero, colors, fonts, heroGradient, panelShadow, radius, space } from '@/lib/theme'; import { Avatar } from './Avatar'; import { Equalizer } from './Equalizer'; import { Lyrics } from './Lyrics'; @@ -54,15 +53,12 @@ export function NowPlayingCard({ onTogglePin?.(); }; + // Your own song gets the gradient "hero" card; friends' songs sit on plain panels. + const hero = !showUser; return ( - - {/* The card glows with its album art; crossfades when the song changes. */} - {np.track.artworkUrl && ( - - - - )} - + + + {hero && } {showUser && ( @@ -74,11 +70,11 @@ export function NowPlayingCard({ )} - {np.isPlaying && } - + {np.isPlaying && } + {np.isPlaying ? 'Listening now' : `Played ${timeAgo(np.updatedAt)}`} Linking.openURL(np.sourceUrl!) : undefined}> {' · '} {np.sourceApp ?? SERVICE_LABELS[np.source]} @@ -109,30 +105,33 @@ export function NowPlayingCard({ )} + ); } const styles = StyleSheet.create({ card: { borderRadius: radius.lg, - padding: space.md + 2, + padding: space.md + 4, gap: space.sm + 2, - overflow: 'hidden', backgroundColor: colors.surface, borderWidth: 1, - borderColor: colors.border, + borderColor: 'transparent', + ...panelShadow, }, + heroCard: { overflow: 'hidden', shadowColor: colors.accent, shadowOpacity: 0.4, shadowRadius: 22 }, + heroText: { color: 'rgba(255,255,255,0.85)' }, + heroLive: { color: '#ffffff', fontFamily: fonts.bodyMedium }, details: { gap: space.md }, - pinned: { borderColor: 'rgba(251,191,36,0.55)' }, - backdrop: { position: 'absolute', top: '-40%', left: '-40%', width: '180%', height: '180%', opacity: 0.55 }, + pinned: { borderColor: colors.accentLine }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: space.sm }, user: { flexDirection: 'row', alignItems: 'center', gap: space.sm, flexShrink: 1 }, - name: { color: colors.text, fontWeight: '700', fontSize: 15, flexShrink: 1 }, + name: { color: colors.text, fontFamily: fonts.bodyMedium, fontSize: 14.5, flexShrink: 1 }, statusRow: { flexDirection: 'row', alignItems: 'center', gap: space.xs + 2, flexShrink: 1 }, - status: { color: colors.muted, fontSize: 13, flexShrink: 1 }, - live: { color: colors.live, fontWeight: '600' }, - via: { color: colors.muted, fontWeight: '400' }, + status: { color: colors.muted, fontSize: 12.5, fontFamily: fonts.body, flexShrink: 1 }, + live: { color: colors.live, fontFamily: fonts.bodyMedium }, + via: { color: colors.muted, fontFamily: fonts.body }, viaLink: { textDecorationLine: 'underline' }, pin: { color: colors.muted, fontSize: 21, marginLeft: space.xs }, - pinOn: { color: colors.pin, textShadowColor: 'rgba(251,191,36,0.6)', textShadowRadius: 10 }, + pinOn: { color: colors.accent, textShadowColor: 'rgba(255,79,139,0.6)', textShadowRadius: 10 }, }); diff --git a/apps/mobile/src/components/ProfileView.tsx b/apps/mobile/src/components/ProfileView.tsx index cfb7089..6668679 100644 --- a/apps/mobile/src/components/ProfileView.tsx +++ b/apps/mobile/src/components/ProfileView.tsx @@ -7,7 +7,7 @@ import { api } from '@/lib/api'; import { showError } from '@/lib/errors'; import { useRealtime } from '@/lib/useRealtime'; import { useSession } from '@/lib/session'; -import { colors, space } from '@/lib/theme'; +import { colors, fonts, space } from '@/lib/theme'; import { Avatar } from './Avatar'; import { NowPlayingCard } from './NowPlayingCard'; import { RecentPlays } from './RecentPlays'; @@ -162,9 +162,9 @@ export function ProfileView({ username, edges }: { username: string; edges?: ('t const styles = StyleSheet.create({ header: { flexDirection: 'row', alignItems: 'center', gap: space.lg }, - meta: { color: colors.muted, fontSize: 13 }, + meta: { color: colors.muted, fontSize: 13, fontFamily: fonts.body }, counts: { flexDirection: 'row', gap: space.lg, marginTop: 2 }, - count: { color: colors.text, fontWeight: '700' }, + count: { color: colors.text, fontFamily: fonts.display }, quote: { color: colors.text, fontStyle: 'italic', diff --git a/apps/mobile/src/components/ProgressBar.tsx b/apps/mobile/src/components/ProgressBar.tsx index bb76d57..c62d9b0 100644 --- a/apps/mobile/src/components/ProgressBar.tsx +++ b/apps/mobile/src/components/ProgressBar.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react'; import { StyleSheet, Text, View } from 'react-native'; import Animated, { Easing, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; import { formatDuration, playbackPosition, type NowPlayingDTO } from '@musicshare/shared'; -import { colors } from '@/lib/theme'; +import { brandGradient, fonts, useInk } from '@/lib/theme'; /** Where they are in the song. Renders nothing when the source doesn't report a position. */ export function ProgressBar({ np, compact }: { np: NowPlayingDTO; /** Just the bar, no times. */ compact?: boolean }) { @@ -20,6 +20,7 @@ function Bar({ np, compact }: { np: NowPlayingDTO; compact?: boolean }) { return () => clearInterval(id); }, []); + const ink = useInk(); const pos = playbackPosition(np, now - receivedAt); const fraction = pos ? pos.positionMs / pos.durationMs : 0; const width = useSharedValue(fraction); @@ -31,15 +32,15 @@ function Bar({ np, compact }: { np: NowPlayingDTO; compact?: boolean }) { if (!pos) return null; return ( - - - + + + {!ink.hero && } {!compact && ( - {formatDuration(pos.positionMs)} - {formatDuration(pos.durationMs)} + {formatDuration(pos.positionMs)} + {formatDuration(pos.durationMs)} )} @@ -48,8 +49,8 @@ function Bar({ np, compact }: { np: NowPlayingDTO; compact?: boolean }) { const styles = StyleSheet.create({ wrap: { marginTop: 2 }, - track: { height: 4, borderRadius: 2, backgroundColor: 'rgba(255,255,255,0.14)', overflow: 'hidden' }, + track: { height: 4, borderRadius: 2, overflow: 'hidden' }, fill: { height: '100%', borderRadius: 2, overflow: 'hidden' }, times: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 4 }, - time: { color: colors.muted, fontSize: 12, fontVariant: ['tabular-nums'] }, + time: { fontSize: 11.5, fontFamily: fonts.body, fontVariant: ['tabular-nums'] }, }); diff --git a/apps/mobile/src/components/Reactions.tsx b/apps/mobile/src/components/Reactions.tsx index c887812..f674cf7 100644 --- a/apps/mobile/src/components/Reactions.tsx +++ b/apps/mobile/src/components/Reactions.tsx @@ -7,7 +7,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, panelShadow, radius, space } from '@/lib/theme'; /** * Emoji reactions on one play. Tap an emoji to add yours (or take it back); "☺+" @@ -168,13 +168,13 @@ const styles = StyleSheet.create({ paddingHorizontal: 9, paddingVertical: 3, borderRadius: radius.pill, - backgroundColor: 'rgba(255,255,255,0.07)', - borderWidth: StyleSheet.hairlineWidth, - borderColor: 'rgba(255,255,255,0.16)', + backgroundColor: 'rgba(255,255,255,0.1)', + borderWidth: 1, + borderColor: 'transparent', }, - mine: { backgroundColor: 'rgba(123,47,247,0.3)', borderColor: 'rgba(123,47,247,0.75)' }, + mine: { backgroundColor: colors.accentSoft, borderColor: colors.accentLine }, emoji: { fontSize: 16 }, - count: { color: colors.text, fontSize: 12, fontVariant: ['tabular-nums'] }, + count: { color: colors.text, fontSize: 12, fontVariant: ['tabular-nums'], fontFamily: fonts.body }, quick: { flexDirection: 'row', alignSelf: 'flex-start', @@ -182,16 +182,15 @@ const styles = StyleSheet.create({ gap: 2, padding: 4, borderRadius: radius.pill, - backgroundColor: '#1a1535', - borderWidth: StyleSheet.hairlineWidth, - borderColor: 'rgba(255,255,255,0.16)', + backgroundColor: colors.surface2, + ...panelShadow, }, quickItem: { width: 40, height: 40, borderRadius: 20, alignItems: 'center', justifyContent: 'center' }, quickEmoji: { fontSize: 26 }, - backdrop: { flex: 1, backgroundColor: 'rgba(5,3,15,0.6)' }, + backdrop: { flex: 1, backgroundColor: 'rgba(20,18,40,0.6)' }, sheet: { height: '55%', - backgroundColor: '#15112c', + backgroundColor: colors.surface, borderTopLeftRadius: radius.lg, borderTopRightRadius: radius.lg, padding: space.md, @@ -199,7 +198,7 @@ const styles = StyleSheet.create({ }, search: { color: colors.text, - backgroundColor: 'rgba(255,255,255,0.07)', + backgroundColor: colors.surface2, borderRadius: radius.md, paddingHorizontal: space.md, paddingVertical: 10, diff --git a/apps/mobile/src/components/RecentPlays.tsx b/apps/mobile/src/components/RecentPlays.tsx index 30bdb02..78a2a27 100644 --- a/apps/mobile/src/components/RecentPlays.tsx +++ b/apps/mobile/src/components/RecentPlays.tsx @@ -2,7 +2,7 @@ import { Image } from 'expo-image'; import { Linking, Pressable, StyleSheet, Text, View } from 'react-native'; import { openUrl, timeAgo, type ProfileDTO, type RecentPlayDTO, type StreamingService } from '@musicshare/shared'; import { showError } from '@/lib/errors'; -import { colors, radius, space } from '@/lib/theme'; +import { colors, fonts, radius, space } from '@/lib/theme'; import { Card, Heading } from './ui'; import { Reactions } from './Reactions'; @@ -64,9 +64,9 @@ const styles = StyleSheet.create({ divider: { borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: colors.border }, art: { width: 40, height: 40, borderRadius: radius.sm, backgroundColor: colors.surface2 }, artEmpty: { alignItems: 'center', justifyContent: 'center' }, - title: { color: colors.text, fontWeight: '600', fontSize: 14 }, - artist: { color: colors.muted, fontSize: 13 }, + title: { color: colors.text, fontFamily: fonts.bodyMedium, fontSize: 14 }, + artist: { color: colors.muted, fontSize: 13, fontFamily: fonts.body }, time: { color: colors.muted, fontSize: 12 }, reactions: { marginLeft: 40 + space.md, marginTop: -space.sm, marginBottom: space.xs }, - credit: { color: colors.muted, fontSize: 12, marginTop: space.sm }, + credit: { color: colors.muted, fontSize: 12, marginTop: space.sm, fontFamily: fonts.body }, }); diff --git a/apps/mobile/src/components/TrackCard.tsx b/apps/mobile/src/components/TrackCard.tsx index 86690b5..3df70d5 100644 --- a/apps/mobile/src/components/TrackCard.tsx +++ b/apps/mobile/src/components/TrackCard.tsx @@ -5,7 +5,7 @@ import { SERVICE_LABELS, openUrl, shareText, trackUrl, viewerServices, type Stre import { WEB_URL } from '@/lib/config'; import { useSession } from '@/lib/session'; import { showError } from '@/lib/errors'; -import { colors, fonts, radius, space } from '@/lib/theme'; +import { colors, fonts, radius, space, useInk } from '@/lib/theme'; import { Button } from './ui'; /** @@ -50,6 +50,8 @@ function PrimaryButton({ track, preferred, label }: { track: TrackDTO; preferred /** Quieter links for your other services, and Share. */ export function ServiceLinks({ track, preferred }: { track: TrackDTO; preferred: StreamingService | null | undefined }) { const { me } = useSession(); + const ink = useInk(); + const chip = [styles.service, ink.hero && styles.serviceHero]; const others = viewerServices(me).filter((s) => s !== preferred); return ( @@ -57,17 +59,17 @@ export function ServiceLinks({ track, preferred }: { track: TrackDTO; preferred: const { url, exact } = openUrl(track, s); return ( open(url)} hitSlop={6} accessibilityRole="link"> - {SERVICE_LABELS[s]} + {SERVICE_LABELS[s]} ); })} {me && ( router.push(`/send/${track.id}`)} hitSlop={6} accessibilityRole="button" accessibilityLabel="Send to friends"> - Send ✉ + Send ✉ )} shareTrack(track, me?.username)} hitSlop={6} accessibilityRole="button" accessibilityLabel="Share this song"> - Share ↗ + Share ↗ ); @@ -88,6 +90,7 @@ export function TrackRow({ expanded: boolean; onToggle: () => void; }) { + const ink = useInk(); return ( @@ -98,7 +101,7 @@ export function TrackRow({ {track.artist} - {expanded && track.album ? · {track.album} : null} + {expanded && track.album ? · {track.album} : null} @@ -109,7 +112,7 @@ export function TrackRow({ accessibilityRole="button" accessibilityLabel={expanded ? 'Show less' : 'Show more'} style={styles.chevron}> - ⌄ + ⌄ ); @@ -143,32 +146,29 @@ const styles = StyleSheet.create({ track: { flexDirection: 'row', gap: space.md + 2, alignItems: 'flex-start' }, row: { flexDirection: 'row', alignItems: 'center', gap: space.sm }, rowMain: { flex: 1, minWidth: 0, flexDirection: 'row', alignItems: 'center', gap: space.md }, - artwork: { - backgroundColor: colors.surface2, - borderWidth: StyleSheet.hairlineWidth, - borderColor: 'rgba(255,255,255,0.12)', - }, - artworkEmpty: { alignItems: 'center', justifyContent: 'center', backgroundColor: '#3a1a6e' }, + artwork: { backgroundColor: colors.surface2 }, + artworkEmpty: { alignItems: 'center', justifyContent: 'center', backgroundColor: '#4b3f78' }, note: { color: 'rgba(255,255,255,0.85)' }, body: { flex: 1, minWidth: 0, gap: 2 }, title: { color: colors.text, fontFamily: fonts.display, fontSize: 16, letterSpacing: -0.2 }, titleBig: { fontSize: 18 }, - artist: { color: colors.text, opacity: 0.85, fontSize: 14 }, + artist: { color: colors.text, opacity: 0.85, fontSize: 13.5, fontFamily: fonts.body }, muted: { color: colors.muted }, chevron: { paddingHorizontal: space.xs, alignSelf: 'stretch', justifyContent: 'center' }, chevronText: { color: colors.muted, fontSize: 22, lineHeight: 24 }, - share: { backgroundColor: 'rgba(123,47,247,0.28)', borderColor: 'rgba(123,47,247,0.5)' }, + share: { backgroundColor: colors.accentSoft, color: '#ffd1e1' }, + shareHero: { backgroundColor: 'rgba(255,255,255,0.32)' }, + serviceHero: { backgroundColor: 'rgba(255,255,255,0.2)', color: '#ffffff' }, primary: { alignSelf: 'flex-start', marginTop: space.sm }, services: { flexDirection: 'row', flexWrap: 'wrap', gap: space.xs + 2, marginTop: space.sm + 2 }, service: { color: colors.text, fontSize: 12, - paddingHorizontal: space.sm + 2, - paddingVertical: 4, + fontFamily: fonts.bodyMedium, + paddingHorizontal: space.sm + 4, + paddingVertical: 5, borderRadius: radius.pill, overflow: 'hidden', - backgroundColor: 'rgba(255,255,255,0.08)', - borderWidth: StyleSheet.hairlineWidth, - borderColor: 'rgba(255,255,255,0.12)', + backgroundColor: colors.surface2, }, }); diff --git a/apps/mobile/src/components/setup/SpotifySetup.tsx b/apps/mobile/src/components/setup/SpotifySetup.tsx index 16ed46c..fb054c2 100644 --- a/apps/mobile/src/components/setup/SpotifySetup.tsx +++ b/apps/mobile/src/components/setup/SpotifySetup.tsx @@ -5,7 +5,7 @@ import type { ConnectionDTO } from '@musicshare/shared'; import { api } from '@/lib/api'; import { SignInError, openSignupPage, openSpotifyLinkPage, signInWithService } from '@/lib/connect'; import { successHaptic } from '@/lib/haptics'; -import { colors, radius, space } from '@/lib/theme'; +import { colors, fonts, radius, space } from '@/lib/theme'; import { Body, Button, Field } from '../ui'; import { ErrorText, SetupActions, SetupHeader, type SetupProps } from './common'; @@ -153,9 +153,9 @@ function Option({ selected, onPress, title, badge, detail }: { selected: boolean const styles = StyleSheet.create({ option: { borderRadius: radius.md, borderWidth: 1, borderColor: colors.border, backgroundColor: colors.surface, padding: space.md, gap: space.xs }, - optionSelected: { borderColor: colors.accent, backgroundColor: 'rgba(124,77,255,0.16)' }, + optionSelected: { borderColor: colors.accent, backgroundColor: colors.accentSoft }, optionHead: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }, - optionTitle: { color: colors.text, fontWeight: '700', fontSize: 16 }, - badge: { color: colors.accent2, fontSize: 12, fontWeight: '700' }, - small: { fontSize: 13 }, + optionTitle: { color: colors.text, fontFamily: fonts.display, fontSize: 16 }, + badge: { color: colors.accent, fontSize: 12, fontFamily: fonts.display }, + small: { fontSize: 13, fontFamily: fonts.body }, }); diff --git a/apps/mobile/src/components/setup/common.tsx b/apps/mobile/src/components/setup/common.tsx index 8ad017d..6800012 100644 --- a/apps/mobile/src/components/setup/common.tsx +++ b/apps/mobile/src/components/setup/common.tsx @@ -1,5 +1,5 @@ import { StyleSheet, View } from 'react-native'; -import { colors, space } from '@/lib/theme'; +import { colors, fonts, space } from '@/lib/theme'; import { Body, Button, Card, Title } from '../ui'; export interface SetupProps { @@ -55,5 +55,5 @@ export const ErrorText = ({ children }: { children: string | null }) => children ? {children} : null; const styles = StyleSheet.create({ - point: { color: colors.text, fontSize: 14 }, + point: { color: colors.text, fontSize: 14, fontFamily: fonts.body }, }); diff --git a/apps/mobile/src/components/ui.tsx b/apps/mobile/src/components/ui.tsx index 27c0bc6..a12951e 100644 --- a/apps/mobile/src/components/ui.tsx +++ b/apps/mobile/src/components/ui.tsx @@ -1,5 +1,5 @@ import { LinearGradient } from 'expo-linear-gradient'; -import { forwardRef, useState } from 'react'; +import { forwardRef, useContext, useState } from 'react'; import { ActivityIndicator, Pressable, @@ -15,7 +15,7 @@ import { } from 'react-native'; import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'; import { tapHaptic } from '@/lib/haptics'; -import { brandGradient, colors, fonts, radius, space } from '@/lib/theme'; +import { OnHero, brandGradient, colors, fonts, panelShadow, radius, space } from '@/lib/theme'; export function Title({ style, ...props }: TextProps) { return ; @@ -65,17 +65,19 @@ export function Button({ ...props }: PressableProps & { title: string; variant?: Variant; loading?: boolean; small?: boolean; style?: ViewProps['style'] }) { const press = usePressScale(); + const hero = useContext(OnHero); const inactive = disabled || loading; const handlePress = (e: GestureResponderEvent) => { if (variant === 'primary') tapHaptic(); onPress?.(e); }; + // On the pink hero card a pink button would vanish: primary goes white with pink text instead. + const heroPrimary = hero && variant === 'primary'; + const textColor = heroPrimary ? colors.accent : variant === 'secondary' ? colors.text : colors.accentText; const label = loading ? ( - + ) : ( - - {title} - + {title} ); return ( @@ -86,9 +88,15 @@ export function Button({ onPressIn={press.onPressIn} onPressOut={press.onPressOut} onPress={handlePress} - style={[styles.button, small && styles.buttonSmall, variant === 'danger' && styles.buttonDanger, variant === 'primary' && styles.buttonPrimary]} + style={[ + styles.button, + small && styles.buttonSmall, + variant === 'danger' && styles.buttonDanger, + variant === 'primary' && styles.buttonPrimary, + hero && (variant === 'primary' ? styles.buttonHeroPrimary : styles.buttonHero), + ]} {...props}> - {variant === 'primary' && ( + {variant === 'primary' && !hero && ( )} {label} @@ -183,20 +191,18 @@ export function Chip({ label, selected, onPress }: { label: string; selected?: b } const styles = StyleSheet.create({ - title: { color: colors.text, fontSize: 30, fontFamily: fonts.display, letterSpacing: -0.6 }, - heading: { color: colors.text, fontSize: 18, fontFamily: fonts.display, marginBottom: space.xs }, - body: { color: colors.text, fontSize: 15, lineHeight: 21 }, + title: { color: colors.text, fontSize: 26, fontFamily: fonts.display, letterSpacing: -0.4 }, + heading: { color: colors.text, fontSize: 17, fontFamily: fonts.display, marginBottom: space.xs }, + body: { color: colors.text, fontSize: 14.5, lineHeight: 21, fontFamily: fonts.body }, muted: { color: colors.muted }, card: { backgroundColor: colors.surface, - borderColor: colors.border, - borderWidth: StyleSheet.hairlineWidth, borderRadius: radius.lg, - padding: space.lg, + padding: space.lg + 2, gap: space.md, - overflow: 'hidden', + ...panelShadow, }, - cardHighlighted: { borderColor: 'rgba(251,191,36,0.55)', borderWidth: 1 }, + cardHighlighted: { borderColor: colors.accentLine, borderWidth: 1 }, row: { flexDirection: 'row', alignItems: 'center', gap: space.sm, flexWrap: 'wrap' }, button: { minHeight: 46, @@ -205,51 +211,49 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', backgroundColor: colors.surface2, - borderWidth: 1, - borderColor: colors.borderStrong, overflow: 'hidden', }, buttonSmall: { minHeight: 36, paddingHorizontal: space.md + 2 }, buttonPrimary: { - borderColor: 'transparent', - shadowColor: '#5b4cf0', - shadowOpacity: 0.6, + shadowColor: colors.accent, + shadowOpacity: 0.45, shadowRadius: 14, shadowOffset: { width: 0, height: 6 }, elevation: 6, }, - buttonDanger: { backgroundColor: colors.danger, borderColor: colors.danger }, - buttonText: { color: colors.text, fontWeight: '700', fontSize: 15 }, + buttonHero: { backgroundColor: 'rgba(255,255,255,0.22)' }, + buttonHeroPrimary: { backgroundColor: '#ffffff', shadowColor: '#7a1e4a', shadowOpacity: 0.25 }, + buttonDanger: { backgroundColor: colors.danger }, + buttonText: { color: colors.text, fontFamily: fonts.bodyMedium, fontSize: 14.5 }, buttonTextSmall: { fontSize: 13 }, - label: { color: colors.text, fontWeight: '600', fontSize: 14 }, + label: { color: colors.text, fontFamily: fonts.bodyMedium, fontSize: 14 }, listRow: { flexDirection: 'row', alignItems: 'center', gap: space.md, paddingVertical: 13, paddingHorizontal: 2, borderRadius: radius.sm }, listDivider: { borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: colors.border }, - listLabel: { color: colors.text, fontSize: 15, fontWeight: '600' }, - listValue: { flex: 1, textAlign: 'right', color: colors.muted, fontSize: 14 }, - listChevron: { color: colors.muted, fontSize: 20, lineHeight: 20 }, + listLabel: { color: colors.text, fontSize: 14.5, fontFamily: fonts.bodyMedium }, + listValue: { flex: 1, textAlign: 'right', color: colors.muted, fontSize: 13.5, fontFamily: fonts.body }, + listChevron: { color: colors.muted, fontSize: 20, lineHeight: 20, fontFamily: fonts.body }, reveal: { position: 'absolute', right: 0, top: 0, bottom: 0, justifyContent: 'center', paddingHorizontal: space.md }, - revealText: { color: colors.accent2, fontWeight: '600', fontSize: 14 }, + revealText: { color: colors.accent, fontFamily: fonts.bodyMedium, fontSize: 13.5 }, input: { minHeight: 48, paddingHorizontal: space.md + 2, paddingVertical: space.sm, borderRadius: radius.md, borderWidth: 1, - borderColor: colors.border, + borderColor: 'transparent', backgroundColor: colors.surface2, color: colors.text, - fontSize: 15, + fontSize: 14.5, + fontFamily: fonts.body, }, - inputFocused: { borderColor: colors.accent, backgroundColor: 'rgba(124,77,255,0.10)' }, + inputFocused: { borderColor: colors.accentLine, backgroundColor: '#463f62' }, chip: { - paddingHorizontal: space.md, + paddingHorizontal: space.md + 2, paddingVertical: space.sm, borderRadius: radius.pill, - borderWidth: 1, - borderColor: colors.border, backgroundColor: colors.surface2, overflow: 'hidden', }, - chipSelected: { borderColor: 'transparent' }, - chipText: { color: colors.text, fontWeight: '600', fontSize: 13 }, + chipSelected: {}, + chipText: { color: colors.text, fontFamily: fonts.bodyMedium, fontSize: 13 }, }); diff --git a/apps/mobile/src/lib/theme.ts b/apps/mobile/src/lib/theme.ts index facb8dd..7c21590 100644 --- a/apps/mobile/src/lib/theme.ts +++ b/apps/mobile/src/lib/theme.ts @@ -1,28 +1,62 @@ -/** Mirrors the web palette (apps/web/src/app/globals.css). Brand colours come from the logo. */ +import { createContext, useContext } from 'react'; + +/** + * Mirrors the web palette (apps/web/src/app/globals.css): slate-indigo layers with a pink accent. + * Panels are a shade lighter than the page instead of outlined. + */ export const colors = { - bg: '#07070b', - surface: 'rgba(255,255,255,0.05)', - surfaceSolid: '#12121a', - surface2: 'rgba(255,255,255,0.09)', - border: 'rgba(255,255,255,0.10)', - borderStrong: 'rgba(255,255,255,0.18)', - text: '#f4f3fa', - muted: '#9b99ae', - accent: '#7c4dff', - accent2: '#2f8cff', + bg: '#2a2942', + surface: '#34334f', + surfaceSolid: '#34334f', + surface2: '#403e5e', + border: 'rgba(255,255,255,0.07)', + borderStrong: 'rgba(255,255,255,0.14)', + text: '#f4f3fb', + muted: '#a19fbd', + accent: '#ff4f8b', + accent2: '#8b7dff', accentText: '#ffffff', - live: '#34d399', - danger: '#fb7185', - pin: '#fbbf24', + /** Pink washes for selected chips, "mine" reactions and focus. */ + accentSoft: 'rgba(255,79,139,0.18)', + accentLine: 'rgba(255,79,139,0.6)', + live: '#3ee0a1', + danger: '#ff6b81', + pin: '#ffc95c', } as const; -/** The logo's purple-to-blue, for buttons and highlights. */ -export const brandGradient = ['#7b2ff7', '#5b4cf0', '#2f8cff'] as const; +/** Buttons and selected chips: pink into violet. */ +export const brandGradient = ['#ff4f8b', '#d14fc4', '#8b5cf6'] as const; +/** The big "your song" card: pink, lavender, sky blue (like a holographic card). */ +export const heroGradient = ['#ff4f8b', '#e27ad8', '#a99cf6', '#8fc8ff'] as const; export const fonts = { - display: 'SpaceGrotesk_700Bold', - displayMedium: 'SpaceGrotesk_500Medium', + display: 'Poppins_600SemiBold', + displayMedium: 'Poppins_500Medium', + body: 'Poppins_400Regular', + bodyMedium: 'Poppins_500Medium', } as const; export const space = { xs: 4, sm: 8, md: 12, lg: 16, xl: 24 } as const; -export const radius = { sm: 8, md: 12, lg: 20, pill: 999 } as const; +export const radius = { sm: 10, md: 14, lg: 24, pill: 999 } as const; + +/** Soft drop shadow for panels (the reference separates layers with shadow, not borders). */ +export const panelShadow = { + shadowColor: '#15142a', + shadowOpacity: 0.35, + shadowRadius: 18, + shadowOffset: { width: 0, height: 8 }, + elevation: 4, +} as const; + +/** + * Inside the gradient hero card, text and controls switch to white so they read on pink. + * Components that draw muted text or filled buttons ask useInk() which to use. + */ +export const OnHero = createContext(false); + +export function useInk() { + const hero = useContext(OnHero); + return hero + ? { hero, text: '#ffffff', muted: 'rgba(255,255,255,0.82)', faint: 'rgba(255,255,255,0.28)', live: '#ffffff' } + : { hero, text: colors.text, muted: colors.muted, faint: 'rgba(255,255,255,0.12)', live: colors.live }; +} diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index c512382..f823756 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -1,23 +1,31 @@ :root { - --bg: #07070b; - --surface: rgba(255, 255, 255, 0.045); - --surface-solid: #12121a; - --surface-2: rgba(255, 255, 255, 0.08); - --border: rgba(255, 255, 255, 0.09); - --border-strong: rgba(255, 255, 255, 0.16); - --text: #f4f3fa; - --muted: #9b99ae; - /* Brand: the logo's purple-to-blue. */ - --accent: #7c4dff; - --accent-2: #2f8cff; - --grad: linear-gradient(135deg, #7b2ff7 0%, #5b4cf0 50%, #2f8cff 100%); + /* Slate-indigo layers with a pink accent. Panels are a shade apart instead of outlined. */ + --bg: #2a2942; + --side: #2a2942; + --panel: #34334f; + --card: #34334f; + --surface: var(--card); + --surface-solid: #34334f; + --surface-2: #403e5e; + --border: rgba(255, 255, 255, 0.07); + --border-strong: rgba(255, 255, 255, 0.13); + --text: #f4f3fb; + --muted: #a19fbd; + --accent: #ff4f8b; + --accent-2: #8b7dff; + --accent-soft: rgba(255, 79, 139, 0.16); + --accent-line: rgba(255, 79, 139, 0.55); + --grad: linear-gradient(135deg, #ff4f8b 0%, #d14fc4 55%, #8b5cf6 100%); + /* Your own song's card: pink, lavender, sky blue, like a holographic card. */ + --hero: linear-gradient(135deg, #ff4f8b 0%, #e27ad8 38%, #a99cf6 70%, #8fc8ff 100%); --accent-text: #fff; - --live: #34d399; - --pin: #fbbf24; - --danger: #fb7185; - --radius: 20px; - --font-display: var(--font-space-grotesk), ui-sans-serif, system-ui, sans-serif; - --font-body: var(--font-inter), ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; + --live: #3ee0a1; + --pin: #ffc95c; + --danger: #ff6b81; + --radius: 24px; + --shadow: 0 18px 40px -22px rgba(14, 12, 32, 0.75); + --font-display: var(--font-poppins), ui-sans-serif, system-ui, sans-serif; + --font-body: var(--font-poppins), ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; --spring: cubic-bezier(0.34, 1.56, 0.64, 1); color-scheme: dark; } @@ -29,67 +37,57 @@ body { min-height: 100vh; background: var(--bg); color: var(--text); - font: 15px/1.55 var(--font-body); + font: 14.5px/1.6 var(--font-body); -webkit-font-smoothing: antialiased; } -/* Slowly drifting colour behind everything, so the app never feels static. */ -.ambient { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; } -.ambient::before, .ambient::after { - content: ''; position: absolute; width: 60vmax; height: 60vmax; border-radius: 50%; - filter: blur(90px); opacity: 0.34; -} -.ambient::before { background: #6d28d9; top: -25vmax; left: -15vmax; animation: drift-a 28s ease-in-out infinite alternate; } -.ambient::after { background: #1d6fe0; bottom: -30vmax; right: -20vmax; animation: drift-b 34s ease-in-out infinite alternate; } -@keyframes drift-a { to { transform: translate(18vmax, 12vmax) scale(1.15); } } -@keyframes drift-b { to { transform: translate(-16vmax, -10vmax) scale(0.9); } } a { color: inherit; } -h1, h2, .brand, .display { font-family: var(--font-display); letter-spacing: -0.02em; } +h1, h2, .brand, .display { font-family: var(--font-display); font-weight: 600; letter-spacing: -0.01em; } h1 { font-size: 1.85rem; margin: 0; line-height: 1.15; } h2 { font-size: 1.1rem; margin: 0 0 0.5rem; } input, textarea, select { width: 100%; padding: 0.7rem 0.85rem; - border-radius: 12px; - border: 1px solid var(--border); + border-radius: 14px; + border: 1px solid transparent; background: var(--surface-2); color: var(--text); font: inherit; transition: border-color 0.2s, box-shadow 0.2s, background 0.2s; } input:focus, textarea:focus, select:focus { - outline: none; border-color: var(--accent); background: rgba(124, 77, 255, 0.08); - box-shadow: 0 0 0 4px rgba(124, 77, 255, 0.2); + outline: none; border-color: var(--accent-line); background: #463f62; + box-shadow: 0 0 0 4px var(--accent-soft); } .container { max-width: 680px; margin: 0 auto; padding: 1.75rem 1rem 5rem; } .stack { display: flex; flex-direction: column; gap: 1rem; } .row { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; } -.service-name { display: flex; align-items: center; gap: 0.6rem; font-family: var(--font-display); font-size: 1.12rem; font-weight: 700; margin-bottom: 0.2rem; } +.service-name { display: flex; align-items: center; gap: 0.6rem; font-family: var(--font-display); font-size: 1.05rem; font-weight: 600; margin-bottom: 0.2rem; } .service-badge { width: 1.7rem; height: 1.7rem; border-radius: 0.5rem; display: grid; place-items: center; color: #fff; font-size: 0.95rem; border: 1px solid rgba(255, 255, 255, 0.25); flex-shrink: 0; } .chip-small { display: inline-block; margin-left: 0.5rem; padding: 0.05rem 0.5rem; border-radius: 999px; background: rgba(255, 255, 255, 0.1); font-size: 0.75rem; color: var(--text); } .stats a { color: inherit; text-decoration: none; } .stats a:hover strong { text-decoration: underline; } .menu { position: relative; } -.menu-list { position: absolute; right: 0; top: calc(100% + 0.35rem); z-index: 20; min-width: 12rem; display: flex; flex-direction: column; padding: 0.35rem; border-radius: 0.8rem; background: #1a1535; border: 1px solid rgba(255, 255, 255, 0.12); box-shadow: 0 18px 40px -12px rgba(0, 0, 0, 0.6); } +.menu-list { position: absolute; right: 0; top: calc(100% + 0.35rem); z-index: 20; min-width: 12rem; display: flex; flex-direction: column; padding: 0.35rem; border-radius: 16px; background: var(--surface-2); box-shadow: var(--shadow); } .menu-list button { all: unset; cursor: pointer; padding: 0.55rem 0.75rem; border-radius: 0.55rem; font-size: 0.92rem; } .menu-list button:hover, .menu-list button:focus-visible { background: rgba(255, 255, 255, 0.08); } .menu-list .danger { color: var(--danger); } -.modal { max-width: 26rem; width: calc(100% - 2rem); color: var(--text); background: #15112c; } -.modal::backdrop { background: rgba(5, 3, 15, 0.7); backdrop-filter: blur(4px); } +.modal { max-width: 26rem; width: calc(100% - 2rem); color: var(--text); background: var(--panel); border: 0; } +.modal::backdrop { background: rgba(20, 18, 40, 0.65); backdrop-filter: blur(4px); } .modal p { margin-bottom: 1rem; } .modal .form p { margin: 0; } .pick-list { max-height: 45vh; overflow-y: auto; } .pick-list .checkbox { display: flex; align-items: center; gap: 0.6rem; cursor: pointer; font-weight: 500; } .bell { position: relative; display: grid; place-items: center; width: 38px; height: 38px; border-radius: 999px; margin-left: auto; margin-right: 1rem; - color: var(--muted); background: rgba(255, 255, 255, 0.06); transition: color 0.2s, background 0.2s; + color: var(--text); background: var(--surface-2); transition: color 0.2s, background 0.2s; } -.bell:hover { color: var(--text); background: rgba(255, 255, 255, 0.12); } +.bell:hover { color: var(--accent); background: #4a4868; } .bell-badge { position: absolute; top: -3px; right: -5px; min-width: 18px; height: 18px; padding: 0 4px; border-radius: 9px; - background: var(--danger); color: #fff; font-size: 0.68rem; font-weight: 700; display: grid; place-items: center; + background: var(--accent); color: #fff; font-size: 0.68rem; font-weight: 600; display: grid; place-items: center; } .notifications { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.35rem; } .notification { display: flex; align-items: center; gap: 0.8rem; padding: 0.7rem 0.8rem; border-radius: 14px; text-decoration: none; color: var(--text); } @@ -103,12 +101,12 @@ input:focus, textarea:focus, select:focus { .people .user-chip { min-width: 0; } .admin-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr)); gap: 0.75rem; } .admin-stat { text-align: center; padding: 1rem; } -.admin-stat-value { font-family: var(--font-display); font-size: 1.8rem; font-weight: 700; } +.admin-stat-value { font-family: var(--font-display); font-size: 1.8rem; font-weight: 600; } .reports { list-style: none; padding: 0; margin: 0.75rem 0 0; display: flex; flex-direction: column; gap: 0.75rem; } -.report { padding: 0.85rem; border-radius: 0.8rem; background: rgba(255, 255, 255, 0.04); display: flex; flex-direction: column; gap: 0.5rem; } +.report { padding: 0.85rem; border-radius: 16px; background: var(--surface-2); display: flex; flex-direction: column; gap: 0.5rem; } .report p { margin: 0; } .chip-small.danger { background: rgba(251, 113, 133, 0.18); color: var(--danger); } -.banner { max-width: 680px; margin: 0 auto 0.5rem; padding: 0.6rem 1rem; border-radius: 0.8rem; background: rgba(123, 47, 247, 0.16); border: 1px solid rgba(123, 47, 247, 0.35); font-size: 0.9rem; } +.banner { max-width: 680px; margin: 0 auto 0.5rem; padding: 0.6rem 1rem; border-radius: 0.8rem; background: var(--accent-soft); border: 1px solid var(--accent-line); font-size: 0.9rem; } .banner .link-button { font-size: inherit; color: var(--text); text-decoration: underline; } .footer { max-width: 680px; margin: 0 auto; padding: 0 1rem 2.5rem; display: flex; gap: 1.25rem; justify-content: center; font-size: 0.85rem; } .footer a { color: var(--muted); text-decoration: none; } @@ -122,11 +120,11 @@ input:focus, textarea:focus, select:focus { .song-art { width: 100%; max-width: 360px; aspect-ratio: 1; object-fit: cover; border-radius: 1.1rem; align-self: center; box-shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.8); } .reactions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem; margin-top: 0.7rem; position: relative; } .recent .reactions { margin: 0.25rem 0 0.4rem 3.4rem; } -.reaction { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.15rem 0.55rem; border-radius: 999px; border: 1px solid rgba(255, 255, 255, 0.14); background: rgba(255, 255, 255, 0.07); color: var(--text); font: inherit; font-size: 0.9rem; cursor: pointer; transition: transform 0.15s, background 0.2s; } -.reaction:hover:not(:disabled) { transform: translateY(-1px); background: rgba(255, 255, 255, 0.13); } +.reaction { display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.15rem 0.6rem; border-radius: 999px; border: 1px solid transparent; background: rgba(255, 255, 255, 0.1); color: var(--text); font: inherit; font-size: 0.9rem; cursor: pointer; transition: transform 0.15s, background 0.2s; } +.reaction:hover:not(:disabled) { transform: translateY(-1px); background: rgba(255, 255, 255, 0.16); } .reaction:active:not(:disabled) { transform: scale(0.9); } .reaction:disabled { cursor: default; } -.reaction.mine { border-color: rgba(123, 47, 247, 0.7); background: rgba(123, 47, 247, 0.28); } +.reaction.mine { border-color: var(--accent-line); background: var(--accent-soft); } .reaction-emoji { font-size: 1.05rem; line-height: 1.3; } .reaction-count { font-size: 0.78rem; font-variant-numeric: tabular-nums; } .reaction-add { color: var(--muted); font-size: 1rem; } @@ -150,10 +148,10 @@ a.reaction { text-decoration: none; } @media (max-width: 520px) { .comment-actions { opacity: 1; } } .reaction-add span { font-size: 0.75rem; margin-left: -0.15rem; } .reaction-add-wrap { position: static; } /* the quick menu anchors to the row, so it never runs off the card */ -.reaction-quick { position: absolute; bottom: calc(100% + 0.4rem); left: 0; z-index: 30; display: flex; align-items: center; gap: 0.15rem; padding: 0.3rem; border-radius: 999px; background: #1a1535; border: 1px solid rgba(255, 255, 255, 0.14); box-shadow: 0 14px 30px -10px rgba(0, 0, 0, 0.7); animation: pop 0.18s ease-out; white-space: nowrap; } +.reaction-quick { position: absolute; bottom: calc(100% + 0.4rem); left: 0; z-index: 30; display: flex; align-items: center; gap: 0.15rem; padding: 0.3rem; border-radius: 999px; background: var(--surface-2); box-shadow: var(--shadow); animation: pop 0.18s ease-out; white-space: nowrap; } .reaction-quick button { all: unset; cursor: pointer; font-size: 1.45rem; padding: 0.2rem 0.3rem; border-radius: 999px; transition: transform 0.15s; } .reaction-quick button:hover { transform: scale(1.25); } -.reaction-quick button.mine { background: rgba(123, 47, 247, 0.35); } +.reaction-quick button.mine { background: var(--accent-soft); } .reaction-quick .reaction-more { font-size: 0.85rem; color: var(--muted); padding: 0.3rem 0.6rem; } @keyframes pop { from { opacity: 0; transform: translateY(6px) scale(0.92); } to { opacity: 1; transform: none; } } .emoji-search { width: 100%; margin-bottom: 0.6rem; } @@ -167,8 +165,8 @@ a.reaction { text-decoration: none; } .list { display: flex; flex-direction: column; margin: 0.25rem -0.4rem 0; } .list-row { all: unset; box-sizing: border-box; display: flex; align-items: center; gap: 0.75rem; padding: 0.8rem 0.4rem; border-top: 1px solid var(--border); cursor: pointer; border-radius: 0.5rem; transition: background 0.15s; } .list-row:first-child { border-top: 0; } -.list-row:hover, .list-row:focus-visible { background: rgba(255, 255, 255, 0.05); } -.list-label { font-weight: 600; flex-shrink: 0; } +.list-row:hover, .list-row:focus-visible { background: rgba(255, 255, 255, 0.04); } +.list-label { font-weight: 500; flex-shrink: 0; } .list-value { flex: 1; min-width: 0; text-align: right; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .list-chevron { color: var(--muted); font-size: 1.2rem; line-height: 1; transition: transform 0.2s; margin-left: auto; } .list-value + .list-chevron { margin-left: 0; } @@ -186,25 +184,22 @@ a.reaction { text-decoration: none; } .grow { flex: 1; min-width: 0; } .muted { color: var(--muted); } .error { color: var(--danger); margin: 0; } -.notice { - background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 0.85rem 1rem; margin: 0; - backdrop-filter: blur(16px); -} -.section-label { margin: 0; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.12em; color: var(--muted); font-weight: 700; } +.notice { background: var(--card); border-radius: 18px; padding: 0.9rem 1.1rem; margin: 0; box-shadow: var(--shadow); } +.section-label { margin: 0; font-size: 0.85rem; color: var(--muted); font-weight: 500; } .narrow { max-width: 420px; margin: 2.5rem auto; } .nav { display: flex; justify-content: space-between; align-items: center; padding: 0.9rem 1.25rem; border-bottom: 1px solid var(--border); - position: sticky; top: 0; background: rgba(7, 7, 11, 0.65); backdrop-filter: blur(18px) saturate(1.4); z-index: 10; + position: sticky; top: 0; background: rgba(42, 41, 66, 0.85); backdrop-filter: blur(18px) saturate(1.4); z-index: 10; } .brand { font-weight: 700; text-decoration: none; font-size: 1.2rem; display: inline-flex; align-items: center; gap: 0.5rem; } -.brand-logo { width: 30px; height: 30px; border-radius: 8px; box-shadow: 0 4px 14px -4px rgba(47, 140, 255, 0.6); } +.brand-logo { width: 30px; height: 30px; border-radius: 8px; box-shadow: 0 4px 14px -4px rgba(255, 79, 139, 0.6); } .nav-links { display: flex; gap: 1.1rem; align-items: center; } .nav-links a { text-decoration: none; color: var(--muted); transition: color 0.2s; } .nav-links a:hover { color: var(--text); } .nav-links a.btn-primary { color: var(--accent-text); } -.nav-links a.active { color: var(--text); font-weight: 600; } +.nav-links a.active { color: var(--accent); font-weight: 500; } /* Phones: main places live in a bottom tab bar, like a native app. */ .tabbar { display: none; } @media (max-width: 640px) { @@ -215,41 +210,40 @@ a.reaction { text-decoration: none; } .tabbar { display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 20; padding: 0.35rem 0.5rem calc(0.35rem + env(safe-area-inset-bottom)); - background: rgba(7, 7, 11, 0.82); backdrop-filter: blur(18px) saturate(1.4); border-top: 1px solid var(--border); + background: rgba(42, 41, 66, 0.92); backdrop-filter: blur(18px) saturate(1.4); border-top: 1px solid var(--border); } .tabbar a { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 0.15rem; padding: 0.35rem 0; color: var(--muted); text-decoration: none; font-size: 0.7rem; font-weight: 600; transition: color 0.2s; } - .tabbar a.active { color: var(--text); } - .tabbar a.active svg { color: #8b9dff; } + .tabbar a.active { color: var(--accent); } body:has(.tabbar) .container { padding-bottom: 6.5rem; } body:has(.tabbar) .footer { padding-bottom: 6rem; } } .btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem; - padding: 0.55rem 1.05rem; border-radius: 999px; border: 1px solid var(--border-strong); - background: var(--surface-2); color: var(--text); font: inherit; font-weight: 600; + padding: 0.55rem 1.1rem; border-radius: 999px; border: 1px solid transparent; + background: var(--surface-2); color: var(--text); font: inherit; font-weight: 500; text-decoration: none; cursor: pointer; white-space: nowrap; transition: transform 0.25s var(--spring), border-color 0.2s, background 0.2s, box-shadow 0.25s, filter 0.2s; } -.btn:hover { border-color: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.12); transform: translateY(-1px); } +.btn:hover { background: #4a4868; transform: translateY(-1px); } .btn:active { transform: scale(0.96); } .btn:disabled { opacity: 0.55; cursor: default; transform: none; } .btn-primary { background: var(--grad); border-color: transparent; color: var(--accent-text); - box-shadow: 0 6px 22px -6px rgba(91, 76, 240, 0.6); + box-shadow: 0 8px 22px -8px rgba(255, 79, 139, 0.7); } -.btn-primary:hover { background: var(--grad); filter: brightness(1.08); box-shadow: 0 10px 28px -6px rgba(47, 140, 255, 0.65); } +.btn-primary:hover { background: var(--grad); filter: brightness(1.08); box-shadow: 0 12px 28px -8px rgba(255, 79, 139, 0.75); } .btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; } .btn-small { padding: 0.3rem 0.75rem; font-size: 0.82rem; } .link-button { background: none; border: 0; color: var(--muted); font: inherit; cursor: pointer; padding: 0; transition: color 0.2s; } .link-button:hover { color: var(--text); } .card { - position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); - padding: 1.1rem; backdrop-filter: blur(20px) saturate(1.3); overflow: hidden; + position: relative; background: var(--card); border-radius: var(--radius); + padding: 1.25rem; box-shadow: var(--shadow); } .card-header { position: relative; display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; margin-bottom: 0.85rem; } .card-header .user-chip { flex-shrink: 0; } @@ -263,45 +257,46 @@ a.reaction { text-decoration: none; } .services-picker legend { font-weight: 600; font-size: 0.9rem; padding: 0; margin-bottom: 0.35rem; } .form label.checkbox { flex-direction: row; align-items: center; gap: 0.5rem; font-weight: 500; } -/* Now-playing cards take their colour from the album art. */ -.np-card { border-color: rgba(255, 255, 255, 0.11); padding: 0.9rem 1rem; } -.np-card .card-header { margin-bottom: 0.65rem; } -.np-card .reactions { margin-top: 0.55rem; } -.np-card.pinned { border-color: rgba(251, 191, 36, 0.45); box-shadow: 0 0 0 1px rgba(251, 191, 36, 0.15), 0 18px 40px -20px rgba(251, 191, 36, 0.35); } -.np-backdrop { - position: absolute; inset: -40%; z-index: 0; background-size: cover; background-position: center; - filter: blur(40px) saturate(2.2) brightness(1.1); opacity: 0.6; transform: scale(1.1); transition: opacity 0.6s; -} -.np-card.playing .np-backdrop { animation: breathe 9s ease-in-out infinite alternate; } -@keyframes breathe { to { transform: scale(1.3) rotate(8deg); opacity: 0.75; } } -.np-card::after { - content: ''; position: absolute; inset: 0; z-index: 0; - background: linear-gradient(180deg, rgba(7, 7, 11, 0.05), rgba(7, 7, 11, 0.45)); -} -.np-card > *:not(.np-backdrop) { position: relative; z-index: 1; } +.np-card { padding: 1rem 1.15rem; border: 1px solid transparent; } +.np-card .card-header { margin-bottom: 0.7rem; } +.np-card .reactions { margin-top: 0.6rem; } +.np-card.pinned { border-color: var(--accent-line); } + +/* Your own song: the gradient "hero" card, with everything on it in white. */ +.np-hero { background: var(--hero); overflow: hidden; box-shadow: 0 24px 50px -24px rgba(255, 79, 139, 0.75); color: #fff; } +.np-hero .muted, .np-hero .status, .np-hero .status-live, .np-hero .progress-times, .np-hero .lyrics-note, .np-hero .lyrics-source { color: rgba(255, 255, 255, 0.85); } +.np-hero .status-live { font-weight: 600; color: #fff; } +.np-hero .progress-track { background: rgba(255, 255, 255, 0.3); } +.np-hero .progress-fill { background: #fff; } +.np-hero .btn-primary { background: #fff; color: var(--accent); box-shadow: 0 8px 20px -10px rgba(122, 30, 74, 0.5); } +.np-hero .btn-primary:hover { background: #fff; filter: none; } +.np-hero .btn, .np-hero .service-link, .np-hero .chevron, .np-hero .reaction { background: rgba(255, 255, 255, 0.24); color: #fff; } +.np-hero .chevron:hover, .np-hero .service-link:hover, .np-hero .reaction:hover:not(:disabled) { background: rgba(255, 255, 255, 0.36); color: #fff; } +.np-hero .track-artist, .np-hero a { color: #fff; } +.np-hero .artwork { box-shadow: 0 12px 26px -12px rgba(80, 20, 70, 0.6); } .pin { background: none; border: 0; cursor: pointer; font-size: 1.25rem; line-height: 1; color: var(--muted); padding: 0.15rem 0.25rem; transition: color 0.2s; } -.pin:hover, .pin.pinned { color: var(--pin); } -.pin.pinned { text-shadow: 0 0 14px rgba(251, 191, 36, 0.6); } +.pin:hover, .pin.pinned { color: var(--accent); } +.pin.pinned { text-shadow: 0 0 14px rgba(255, 79, 139, 0.6); } -.user-chip { display: inline-flex; align-items: center; gap: 0.55rem; text-decoration: none; font-weight: 600; } +.user-chip { display: inline-flex; align-items: center; gap: 0.55rem; text-decoration: none; font-weight: 500; } .user-chip:hover .user-name { text-decoration: underline; } .avatar { border-radius: 50%; object-fit: cover; flex-shrink: 0; display: block; } -.avatar-empty { display: grid; place-items: center; background: linear-gradient(135deg, #4a1278, #1872cf); color: #fff; font-weight: 700; font-family: var(--font-display); } +.avatar-empty { display: grid; place-items: center; background: linear-gradient(135deg, #ff4f8b, #8b5cf6); color: #fff; font-weight: 600; font-family: var(--font-display); } /* A spinning gradient ring while someone is listening. */ .avatar-ring { position: relative; display: inline-grid; place-items: center; border-radius: 50%; padding: 2px; flex-shrink: 0; } .avatar-ring.live::before { content: ''; position: absolute; inset: 0; border-radius: 50%; - background: conic-gradient(from 0deg, #7b2ff7, #2f8cff, #34d399, #7b2ff7); animation: spin 3.5s linear infinite; + background: conic-gradient(from 0deg, #ff4f8b, #8b7dff, #3ee0a1, #ff4f8b); animation: spin 3.5s linear infinite; } -.avatar-ring > * { position: relative; border: 2px solid var(--bg); border-radius: 50%; } +.avatar-ring > * { position: relative; border: 2px solid var(--card); border-radius: 50%; } @keyframes spin { to { transform: rotate(360deg); } } .status { font-size: 0.85rem; color: var(--muted); display: inline-flex; align-items: center; gap: 0.45rem; } -.status-live { color: var(--live); font-weight: 600; } +.status-live { color: var(--live); font-weight: 500; } /* Equalizer bars: dancing while playing, resting when paused. */ .eq { display: inline-flex; align-items: flex-end; gap: 2px; height: 14px; } @@ -314,15 +309,15 @@ a.reaction { text-decoration: none; } .track { display: flex; gap: 1rem; align-items: flex-start; } .artwork { - width: 112px; height: 112px; border-radius: 14px; object-fit: cover; flex-shrink: 0; background: var(--surface-2); - box-shadow: 0 14px 30px -12px rgba(0, 0, 0, 0.75), 0 0 0 1px rgba(255, 255, 255, 0.08); + width: 112px; height: 112px; border-radius: 16px; object-fit: cover; flex-shrink: 0; background: var(--surface-2); + box-shadow: 0 14px 30px -14px rgba(10, 8, 30, 0.8); transition: transform 0.4s var(--spring); } .track:hover .artwork { transform: scale(1.04) rotate(-1deg); } -.artwork-empty { display: grid; place-items: center; font-size: 2rem; color: rgba(255, 255, 255, 0.85); background: linear-gradient(135deg, #4a1278, #1872cf); } +.artwork-empty { display: grid; place-items: center; font-size: 2rem; color: rgba(255, 255, 255, 0.85); background: linear-gradient(135deg, #4b3f78, #6a4c9c); } .artwork-sm { width: 44px; height: 44px; border-radius: 9px; object-fit: cover; flex-shrink: 0; box-shadow: 0 6px 14px -8px rgba(0, 0, 0, 0.8); } .track-body { min-width: 0; flex: 1; } -.track-title { font-family: var(--font-display); font-weight: 700; font-size: 1.15rem; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.track-title { font-family: var(--font-display); font-weight: 600; font-size: 1.15rem; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .track-artist { color: var(--text); opacity: 0.82; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .progress { margin-top: 0.85rem; } .track-row { display: flex; align-items: center; gap: 0.6rem; } @@ -336,9 +331,9 @@ a.reaction { text-decoration: none; } .track-row:hover .artwork { transform: scale(1.04) rotate(-1deg); } .chevron { display: grid; place-items: center; width: 32px; height: 32px; flex-shrink: 0; border-radius: 999px; border: 0; cursor: pointer; - background: rgba(255, 255, 255, 0.06); color: var(--muted); transition: transform 0.3s var(--spring), color 0.2s, background 0.2s; + background: var(--surface-2); color: var(--muted); transition: transform 0.3s var(--spring), color 0.2s, background 0.2s; } -.chevron:hover { color: var(--text); background: rgba(255, 255, 255, 0.12); } +.chevron:hover { color: var(--text); background: #4a4868; } .chevron.open { transform: rotate(180deg); } .np-card .progress { margin-top: 0.65rem; } .np-details { overflow: hidden; } @@ -348,37 +343,37 @@ a.reaction { text-decoration: none; } .lyric { margin: 0; line-height: 1.45; transition: opacity 0.3s, font-size 0.3s; } .lyric-past { opacity: 0.4; } .lyric-next { opacity: 0.65; } -.lyric-now { font-family: var(--font-display); font-weight: 700; font-size: 1.08rem; } +.lyric-now { font-family: var(--font-display); font-weight: 600; font-size: 1.08rem; } .lyrics-note { margin: 0.9rem 0 0; font-size: 0.85rem; } .lyrics-footer { display: flex; align-items: center; justify-content: space-between; margin-top: 0.5rem; font-size: 0.82rem; } .lyrics-source { margin-left: auto; font-size: 0.72rem; } -.progress-track { height: 4px; border-radius: 999px; background: rgba(255, 255, 255, 0.14); overflow: hidden; } +.progress-track { height: 4px; border-radius: 999px; background: rgba(255, 255, 255, 0.12); overflow: hidden; } .progress-fill { height: 100%; border-radius: inherit; background: var(--grad); transition: width 1s linear; } .progress-times { display: flex; justify-content: space-between; margin-top: 0.3rem; font-size: 0.75rem; color: var(--muted); font-variant-numeric: tabular-nums; } @media (prefers-reduced-motion: reduce) { .progress-fill { transition: none; } } .track-actions { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin-top: 0.8rem; } .service-link { - font-size: 0.76rem; color: var(--text); text-decoration: none; padding: 0.2rem 0.55rem; border-radius: 999px; - background: rgba(255, 255, 255, 0.07); border: 1px solid rgba(255, 255, 255, 0.08); transition: background 0.2s, transform 0.25s var(--spring); + font-size: 0.76rem; color: var(--text); text-decoration: none; padding: 0.25rem 0.7rem; border-radius: 999px; + background: var(--surface-2); transition: background 0.2s, transform 0.25s var(--spring); } -.service-link:hover { background: rgba(255, 255, 255, 0.16); transform: translateY(-1px); } +.service-link:hover { background: #4a4868; transform: translateY(-1px); } .service-link.inexact { opacity: 0.55; } .profile-header { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 0.75rem; padding: 0.5rem 0 0.75rem; } .profile-header h1 { font-size: 2.1rem; } .profile-header .bio { margin: 0; max-width: 30rem; } .stats { display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center; } -.stats span { padding: 0.3rem 0.8rem; border-radius: 999px; background: var(--surface-2); border: 1px solid var(--border); color: var(--muted); font-size: 0.88rem; } +.stats span { padding: 0.3rem 0.9rem; border-radius: 999px; background: var(--surface-2); color: var(--muted); font-size: 0.88rem; } .stats strong { color: var(--text); font-family: var(--font-display); } .featured blockquote { margin: 0.9rem 0 0; padding: 0.6rem 0.9rem; border-left: 3px solid transparent; font-style: italic; - border-image: var(--grad) 1; background: rgba(255, 255, 255, 0.04); + border-image: var(--grad) 1; background: var(--surface-2); } .search { position: relative; } -.search-results { list-style: none; margin: 0.5rem 0 0; padding: 0.4rem; background: var(--surface-solid); border: 1px solid var(--border); border-radius: 14px; } +.search-results { list-style: none; margin: 0.5rem 0 0; padding: 0.4rem; background: var(--surface-2); border-radius: 16px; box-shadow: var(--shadow); position: absolute; left: 0; right: 0; z-index: 25; } .search-results li { padding: 0.4rem 0.45rem; border-radius: 10px; transition: background 0.15s; } -.search-results li:hover { background: var(--surface-2); } +.search-results li:hover { background: #4a4868; } .result { display: flex; gap: 0.6rem; align-items: center; background: none; border: 0; color: inherit; font: inherit; cursor: pointer; width: 100%; text-align: left; padding: 0; } .recent { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; } .recent-row { display: flex; align-items: center; gap: 0.8rem; padding: 0.5rem 0.4rem; text-decoration: none; border-radius: 12px; transition: background 0.15s, transform 0.25s var(--spring); } @@ -386,11 +381,11 @@ a.recent-row:hover { background: var(--surface-2); transform: translateX(3px); } .recent li + li { border-top: 1px solid var(--border); } .recent-text { display: flex; flex-direction: column; min-width: 0; flex: 1; } .recent-text > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.recent-title { font-weight: 600; } +.recent-title { font-weight: 500; } .recent-time { font-size: 0.8rem; white-space: nowrap; } .artwork-sm.artwork-empty { display: grid; place-items: center; background: var(--surface-2); color: var(--muted); } .qr-card { display: flex; flex-direction: column; align-items: center; gap: 0.75rem; text-align: center; } -.qr { border-radius: 14px; box-shadow: 0 20px 50px -20px rgba(47, 140, 255, 0.6); } +.qr { border-radius: 16px; box-shadow: 0 20px 50px -20px rgba(255, 79, 139, 0.6); } .credit { margin: 0.6rem 0 0; font-size: 0.8rem; } .picked { display: flex; gap: 0.75rem; align-items: center; } @@ -411,12 +406,12 @@ a.recent-row:hover { background: var(--surface-2); transform: translateX(3px); } .hero { padding: 4.5rem 0 3rem; display: flex; flex-direction: column; gap: 1.25rem; } .hero h1 { font-size: 3.2rem; letter-spacing: -0.04em; line-height: 1.02; } .gradient-text { - background: linear-gradient(90deg, #a78bfa, #7dd3fc, #60a5fa, #a78bfa); background-size: 300% 100%; + background: linear-gradient(90deg, #ff4f8b, #e27ad8, #8fc8ff, #ff4f8b); background-size: 300% 100%; -webkit-background-clip: text; background-clip: text; color: transparent; animation: shimmer 8s linear infinite; } @keyframes shimmer { to { background-position: 300% 0; } } .hero p { color: var(--muted); font-size: 1.15rem; margin: 0; max-width: 34rem; } -.hero .eq { height: 42px; gap: 5px; color: #8b9dff; } +.hero .eq { height: 42px; gap: 5px; color: var(--accent); } .hero .eq span { width: 8px; border-radius: 4px; } @media (max-width: 480px) { @@ -428,3 +423,68 @@ a.recent-row:hover { background: var(--surface-2); transform: translateX(3px); } @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } } + +/* Signed in on wide screens: sidebar | page in a rounded panel | your column (components/Shell.tsx). */ +.sidebar, .rail { display: none; } +@media (min-width: 900px) { + body:has(.shell) .nav, body:has(.shell) .tabbar { display: none; } + .shell { display: grid; grid-template-columns: 232px minmax(0, 1fr); min-height: 100vh; } + .shell-main { min-width: 0; padding: 1rem 1rem 0 0; display: flex; flex-direction: column; } + /* The page sits in a lighter panel; cards inside it are a shade darker, like the reference. */ + .main-panel { --card: #2c2b45; --surface-2: #3b395a; flex: 1; background: var(--panel); border-radius: 36px; padding: 0.5rem 0; } + .main-panel .container { max-width: 760px; padding: 2rem 2rem 3rem; } + .shell .footer { padding: 1.25rem 1rem 1.5rem; } + + .sidebar { display: flex; flex-direction: column; gap: 2rem; position: sticky; top: 0; height: 100vh; padding: 2.2rem 1.1rem 1.6rem; } + .sidebar-brand { font-family: var(--font-display); font-weight: 600; font-size: 1.45rem; text-decoration: none; color: #b7b4ff; padding-left: 0.9rem; letter-spacing: -0.01em; } + .sidebar-brand span { color: var(--accent); } + .sidebar-nav { display: flex; flex-direction: column; gap: 0.3rem; } + .sidebar-nav a, .sidebar-logout { + display: flex; align-items: center; gap: 0.85rem; padding: 0.75rem 0.95rem; border-radius: 16px; text-decoration: none; + color: var(--muted); font-weight: 400; transition: background 0.2s, color 0.2s; + } + .sidebar-nav a:hover { color: var(--text); background: rgba(255, 255, 255, 0.04); } + .sidebar-nav a.active { color: var(--accent); background: var(--panel); font-weight: 500; } + .sidebar-badge { margin-left: auto; min-width: 20px; height: 20px; padding: 0 6px; border-radius: 10px; background: var(--accent); color: #fff; font-size: 0.7rem; font-weight: 600; display: grid; place-items: center; } + .sidebar-logout { margin-top: auto; background: none; border: 0; font: inherit; font-size: 0.88rem; cursor: pointer; } + .sidebar-logout:hover { color: var(--text); } +} +@media (min-width: 1240px) { + .shell { grid-template-columns: 232px minmax(0, 1fr) 330px; } + .shell-main { padding-right: 0; } + .rail { display: flex; flex-direction: column; gap: 1.6rem; position: sticky; top: 0; height: 100vh; overflow-y: auto; padding: 2rem 1.6rem; } + .rail-me { display: flex; align-items: center; gap: 0.75rem; padding-bottom: 1.3rem; border-bottom: 1px solid var(--border); } + .rail-me .bell { margin: 0 0 0 auto; } + .rail-me-link { display: flex; align-items: center; gap: 0.75rem; min-width: 0; text-decoration: none; } + .rail-me-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.35; } + .rail-me-text strong { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .rail-me-text .muted { font-size: 0.8rem; } + .rail-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.4rem; } + .rail-head h2 { margin: 0; font-size: 1.1rem; } + .pill-link { font-size: 0.78rem; color: var(--muted); text-decoration: none; padding: 0.3rem 0.8rem; border-radius: 999px; background: var(--panel); } + .pill-link:hover { color: var(--text); } + .rail-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; } + .rail-list li + li { border-top: 1px solid var(--border); } + .rail-row { display: flex; align-items: center; gap: 0.8rem; padding: 0.75rem 0.2rem; text-decoration: none; border-radius: 12px; transition: background 0.15s; } + .rail-row:hover { background: rgba(255, 255, 255, 0.03); } + .rail-row-text { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.35; } + .rail-row-text > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .rail-row-title { font-size: 0.88rem; } + .rail-row-text .muted { font-size: 0.74rem; } + .rail-list li.unread .rail-row-title { font-weight: 500; } + .rail-row-end { font-size: 0.8rem; color: var(--muted); white-space: nowrap; } + .rail-art { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; flex-shrink: 0; background: var(--panel); } + .rail-art-empty { display: grid; place-items: center; color: var(--muted); } +} + +/* The feed's greeting and search ("Hi Chris, welcome back"). */ +.page-head { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; } +.page-head-text { display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap; flex: 1; min-width: 0; } +.page-head h1 { font-size: 1.45rem; } +.page-head .muted { font-size: 1rem; } +.page-head .search { flex: 0 1 280px; min-width: 200px; } +.page-head .search input { border-radius: 999px; padding-left: 1.1rem; } +@media (max-width: 640px) { .page-head .search { flex-basis: 100%; } } +.page-section { font-size: 1.15rem; margin: 0.5rem 0 0; } +input[type='checkbox'], input[type='radio'] { width: auto; padding: 0; accent-color: var(--accent); box-shadow: none; } +.services-picker label.checkbox { white-space: nowrap; } diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index eeedc9f..2bb0a0f 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,12 +1,11 @@ import type { Metadata, Viewport } from 'next'; -import { Inter, Space_Grotesk } from 'next/font/google'; +import { Poppins } from 'next/font/google'; import Link from 'next/link'; -import { Nav } from '@/components/Nav'; +import { Shell } from '@/components/Shell'; import { Providers } from '@/components/Providers'; import './globals.css'; -const inter = Inter({ subsets: ['latin'], variable: '--font-inter', display: 'swap' }); -const spaceGrotesk = Space_Grotesk({ subsets: ['latin'], variable: '--font-space-grotesk', display: 'swap' }); +const poppins = Poppins({ subsets: ['latin'], weight: ['400', '500', '600', '700'], variable: '--font-poppins', display: 'swap' }); // Previews need absolute image URLs. In production the API lives at /api, so its origin is the site's. const siteUrl = process.env.SITE_URL ?? new URL(process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3737').origin; @@ -21,23 +20,26 @@ export const metadata: Metadata = { }; export const viewport: Viewport = { - themeColor: '#07070b', + themeColor: '#2a2942', viewportFit: 'cover', }; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - + -
-